Boot.scalaで、
LiftRules.encodeJSessionIdInUrl_? = trueしてやればOK。
Liftで携帯サイト作ってる人とかはアップデートの際に要注意!
ScalaとかObjective-Cとかforce.comとかで開発してます。
LiftRules.encodeJSessionIdInUrl_? = trueしてやればOK。
Note that when using an InputStream generator, chuncked encoding will be used with no Content-Length header
import dispatch._ import dispatch.Http._ import dispatch.mime.Mime._ import org.apache.http.entity.mime.content.ByteArrayBody var req = ... //普通にdispatchのRequest作る req = req next req.add(paramName, new ByteArrayBody(data, contentType, filename)) //ByteArrayBodyを指定
val httpmime = "org.apache.httpcomponents" % "httpmime" % "4.1-beta1"
ログイン画面 | 承認画面 |
---|---|
特に問題なし! | 自動でリダイレクトはされないが、リンクを手動でクリックすれば動作はOK! |
ログイン画面 | 承認画面 |
---|---|
文字化けがひどいが、ID・パスワード欄っぽいところに入れて1つ目のボタン(ログイン)を押せば承認画面に飛ぶ。 | 自動でリダイレクトはされないが、リンクを手動でクリックすれば動作はOK! |
ログイン画面 | 承認画面 |
---|---|
両方とも送信ボタン…。1つ目を押せば承認画面に飛ぶ。 | 文字化けがひどいが、リンクを手動でクリックすれば動作はOK! |
ログイン画面 | 承認画面 |
---|---|
日本語文字化け… | 文字化け&Denyが押せないが、動作はOK! |
ログイン画面 | 承認画面 |
---|---|
デザインは崩れるが、動作はOK! | デザインは崩れるが、動作はOK! |
import dispatch._ import com.google.appengine.api.urlfetch._ import java.net.URL object DispatchHelper { /** * convert dispatch.Request -> String */ implicit def r2s(r: Request) = { r.host.get + r.req.getRequestLine.getUri } /** * convert dispatch.Request -> com.google.appengine.api.urlfetch.HTTPRequest */ implicit def dispatch2gae(r: Request): HTTPRequest = { val isPost = r.req.getMethod == "POST" val g = new HTTPRequest(new URL(r), if (isPost) HTTPMethod.POST else HTTPMethod.GET, FetchOptions.Builder.withDeadline(10)) r.req.getAllHeaders.foreach(h => {g.addHeader(h); println(h)}) r.req match { case p: org.apache.http.client.methods.HttpPost => val payload = new java.io.ByteArrayOutputStream p.getEntity.writeTo(payload) g.setPayload(payload.toByteArray) println(p.getEntity.getContentType) case _ => } g } }
def changeTagName[T >: Node](from: String, to: String)(in: T): T = in match { case Elem(p, l, a, ns, child @ _*) => val label = if (l == from) to else l Elem(p, label, a, ns, child.map(changeTagName(from, to)(_)): _*) case x => x }
scala> val hoge2moge = changeTagName("hoge", "moge") _ scala> hoge2moge: (scala.xml.Node) => scala.xml.Node = <function> scala> hoge2moge(<pre:hoge attr="value">parentText<child:hoge>childtext</child:hoge></pre:hoge>) scala> res17: scala.xml.Node = <pre:moge attr="value">parentText<child:moge>childtext</child:moge></pre:moge>
def changeTagName[T >: Node](changeFunc: (String) => String)(in: T): T = in match { case Elem(p, l, a, ns, child @ _*) => Elem(p, changeFunc(l), a, ns, child.map(changeTagName(changeFunc)(_)): _*) case x => x }
scala> val c = changeTagName(s => if (List("moge", "koge").contains(s)) "bege" else s) _ c: (scala.xml.Node) => scala.xml.Node = <function> scala> c(x) res6: scala.xml.Node = <lage> <hoge> <bege></bege> <bege></bege> </hoge> <hoge> <bege></bege> <bege></bege> </hoge> </lage>
def changeTagName(changeFunc: (String) => String)(in: NodeSeq): NodeSeq = in match { case Elem(p, l, a, ns, child @ _*) => Elem(p, changeFunc(l), a, ns, child.flatMap(changeTagName(changeFunc)(_)): _*) case x => x }
app | ave | max | min |
---|---|---|---|
lift-blank *1 | 6829 | 7338 | 5938 |
nomapper *2 | 6681 | 7105 | 6035 |
nomapper/nojson *3 | 6531 | 7124 | 5705 |
min *4 | 6481 | 7240 | 5957 |
INFO - Service request (GET) / took 3056 Milliseconds次はこれを調べてみることにする。
<filter> <filter-name>AppstatsFilter</filter-name> <display-name>Appstats Filter</display-name> <description>Appstats Filter</description> <filter-class>com.google.appengine.tools.appstats.AppstatsFilter</filter-class> </filter> <filter-mapping> <filter-name>AppstatsFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>AppstatsServlet</servlet-name> <servlet-class> com.google.appengine.tools.appstats.AppstatsServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>AppstatsServlet</servlet-name> <url-pattern>/stats/*</url-pattern> </servlet-mapping>
<security-constraint> <web-resource-collection> <url-pattern>/stats/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> </auth-constraint> </security-constraint>
import net.liftweb.http._ import net.liftweb.common._ import com.google.appengine.api.datastore._ object SessionCleaner { // Iteratorのimplicit conversionは定義されていないようなので自前で定義。 implicit def j2s[A](j: java.util.Iterator[A]) = new scala.collection.jcl.MutableIterator.Wrapper[A](j) private lazy val DSS = DatastoreServiceFactory.getDatastoreService def execute(): Box[LiftResponse] = { var count = 0 try { val q = new Query("_ah_SESSION") q.addFilter("_expires", Query.FilterOperator.LESS_THAN_OR_EQUAL, System.currentTimeMillis) DSS.prepare(q).asIterator.foreach(e => {DSS.delete(e.getKey); count = count + 1}) } finally { println(count + " sessions deleted.") } Full(OkResponse()) } }
LiftRules.statelessDispatchTable.append { case Req("cron" :: "sessionCleaner" :: Nil, _, _) => () => SessionCleaner.execute() }
// Boot.scala の def boot内 LiftRules.statelessDispatchTable.prepend { // *1 case MyReq("venue" :: vid :: _, r) if r.header("User-Agent") == Full("何か") => // *2 () => Full(RedirectResponse("http://foursquare.com/venue/" + vid)) } object MyReq { // *3 def unapply(in: Req): Option[(List[String], HTTPRequest)] = Some((in.path.partPath, in.request)) }
<lift:StatefulTest.liftForm form="POST"> <e:instance/> <e:input/> <e:submit/> </lift:StatefulTest.liftForm>
def liftForm(in: NodeSeq): NodeSeq = { var name = "" def sayHello() = { S.notice("Hello, " + name + ". I'm " + this) redirectTo("/liftSayHello") } bind("e", in, "instance" -> Text("I'm " + this), "input" -> SHtml.text(name, i => name = i), "submit" -> SHtml.submit("say hello", sayHello) ) }
<lift:StatefulTest.myForm> <form action="mySayHello" method="POST"> <e:instance/> <e:key/> <e:input/> <e:submit/> </form> </lift:StatefulTest.myForm>
def myForm(in: NodeSeq): NodeSeq = { S.fmapFunc((a: List[String]) => {registerThisSnippet()})(key => { bind("e", in, "key" -> <input type="hidden" name={key} value="_"/>, // *1 "instance" -> Text("I'm " + this), "input" -> <input type="text" name="name"/>, "submit" -> <input type="submit" value="say hello"/> ) }) }このやり方だと、セッションが続いていれば同じインスタンスのStatefulSnippetが呼ばれ、続いていない場合でも、少なくとも新しいインスタンスのStatefulSnippetで処理は行えます。(CSRFの問題がありますが…)
val $x = e match {case p => (x1, . . . , xn)} val x1 = $x._1 . . . val xn = $x._n※The Scala Language Specification(PDF)のp.36参照(タプルが{}になっているのは古い仕様?なので↑では()に直しています)。
val (x, y) = (1, 2) // ↑は↓に展開され val tmp = (1, 2) match {case (a, b) => (a, b)} val x = tmp._1 val y = tmp._2 // 結局↓と同じ val x = 1 val y = 2
// net/liftweb/http/Req.scala l.275 case class ParamCalcInfo(paramNames: List[String], params: Map[String, List[String]], uploadedFiles: List[FileParamHolder], body: Box[Array[Byte]]) // l.284 class Req(val path: ParsePath, val contextPath: String, val requestType: RequestType, val contentType: Box[String], val request: HTTPRequest, val nanoStart: Long, val nanoEnd: Long, private[http] val paramCalculator: () => ParamCalcInfo, // *2 private[http] val addlParams: Map[String, String]) extends HasParams { // (省略) // l.342 lazy val ParamCalcInfo(paramNames: List[String], // *1 _params: Map[String, List[String]], uploadedFiles: List[FileParamHolder], body: Box[Array[Byte]]) = paramCalculator() // (省略) }
val tmp = paramCalculator() match { case ParamCalcInfo(a, b, c, d) => (a, b, c, d) } val paramNames = tmp._1 val _params = tmp._2 val uploadedFiles = tmp._3 val body = tmp._4
import scala.xml._ implicit def c2s(c: CharSequence): String = c.toString val URLPATTERN = """http://[\d\w\-\./%?=#]+""".r def linkURL(s: String): NodeSeq = URLPATTERN.findFirstMatchIn(s) match { case None => Text(s) case Some(m) => <xml:group>{Text(m.before)}<a href={m.matched}>{m.matched}</a>{linkURL(m.after)}</xml:group> }
scala> linkURL("aaaa http://pomu0325.blogspot.com/ bbbb http://twitter.com/pomu0325 cccc") res21: scala.xml.NodeSeq = aaaa <a href="http://pomu0325.blogspot.com/">http://pomu0325.blogspot.com /</a> bbbb <a href="http://twitter.com/pomu0325">http://twitter.com/pomu0325</a> cccc
// StatefulSnippet.scala def link(to: String, func: () => Any, body: NodeSeq): Elem = SHtml.link(to, () => {registerThisSnippet(); func()}, body)
// SHtml.scala def link(to: String, func: () => Any, body: NodeSeq, attrs: (String, String)*): Elem = { fmapFunc((a: List[String]) => {func(); true})(key => attrs.foldLeft(<a href={to + (if (to.indexOf("?") >= 0) "&" else "?") + key + "=_"}>{body}</a>)(_ % _)) }
<a href="device:location?url=http://www.mb4sq.jp/search"/>
def search(in: NodeSeq): NodeSeq = bind("f", in, "link" -> link("device:location?url=http://localhost/search", ()=>"", Text("search")))こんなコードを書いてみたところ、本来欲しいURLは
override def link(to: String, func: () => Any, body: NodeSeq): Elem = { def insert(s: String, i: String, p: Int) = List(s take p, i, s drop p).mkString if (to.startsWith("device:")) S.fmapFunc((a: List[String]) => {registerThisSnippet(); func()})(key => { val u = if (to.indexOf("&ver=1") > 0) insert(to, ("?" + key + "=_"), to.indexOf("&ver=1")) else to + ("?" + key + "=_") <a href={u}>{body}</a> }) else super.link(to, func, body) }
scala> def insert(s: String, i: String, p: Int) = List(s take p, i, s drop p).mkString insert: (String,String,Int)String scala> insert("abcd", "123", 2) res1: String = ab123cd
scala> def until(b: =>Boolean)(f: =>Any) = while(!b)f until: (=> Boolean)(=> Any)Unitポイントは=>で引数を名前渡しにするとこか。
scala> var i = 0 i: Int = 0 scala> until (i==10) {println(i);i=i+1} 0 1 2 3 4 5 6 7 8 9できることは分かったけど、きっと使わないなぁ…。
import com.google.appengine.api.urlfetch._ val UFS = URLFetchServiceFactory.getURLFetchService() def retryingURLFetch[A](r: HTTPRequest, retry: Int)(f: HTTPResponse => A): A = { try { val res = UFS.fetch(r) res.getResponseCode match { case 500 => if (retry <= 0) f(res) else retryingURLFetch(r, retry - 1)(f) case _ => f(res) } } catch { case e: java.io.IOException => if (retry <= 0) throw e retryingURLFetch(r, retry - 1)(f) } }
import java.net.URL import com.google.appengine.api.urlfetch._ retryingURLFetch(new HTTPRequest(new URL("http://pomu0325.blogspot.com")), 2) { res => println(new String(res.getContent, "utf-8")) }不安定なAPIを呼ぶ時とかに使えそう。
for ($j = 0; $j < $totalItems; $j ++) { $currItem = & $feed->items[$j]; // item title ?> <li> <span style="padding-right:10px;"><?php echo $currItem->get_date('Y年n月d日'); ?></span> <?php if ( !is_null( $currItem->get_link() ) ) { ?> <a href="<?php echo $currItem->get_link(); ?>" target="_blank">
// net/liftweb/http/LiftServlet.scala(l.183) resp match { case Full(cresp) => val resp = cresp.toResponse // *1 LiftResponse.toResponseはInMemoryResponseを返す logIfDump(req, resp) sendResponse(resp, response, Full(req)) //...後略...
// net/liftweb/http/LiftResponse.scala(l.415) InMemoryResponse(ret.getBytes("UTF-8"), headers, cookies, code)
// LiftServlet.scala(l.482) // insure that certain header fields are set val header = insureField(fixHeaders(resp.headers), List(("Content-Type", // *2 LiftRules.determineContentType(pairFromRequest(request))), ("Content-Length", len.toString))) // LiftRules.scala(l.168) @volatile var determineContentType: PartialFunction[(Box[Req], Box[String]), String] = { case (_, Full(accept)) if this.useXhtmlMimeType && accept.toLowerCase.contains("application/xhtml+xml") => "application/xhtml+xml; charset=utf-8" case _ => "text/html; charset=utf-8" }
// net.liftweb.http.LiftRules.scala(l.442) @volatile var calculateXmlHeader: (NodeResponse, Node, Box[String]) => String = { case _ if S.skipXmlHeader => "" case (_, up: Unparsed, _) => "" case (_, _, Empty) | (_, _, Failure(_, _, _)) => "\n" case (_, _, Full(s)) if (s.toLowerCase.startsWith("text/html")) => "\n" case (_, _, Full(s)) if (s.toLowerCase.startsWith("text/xml") || s.toLowerCase.startsWith("text/xhtml") || s.toLowerCase.startsWith("application/xml") || s.toLowerCase.startsWith("application/xhtml+xml")) => "\n" case _ => "" }
def boot { //...省略... LiftRules.responseTransformers.append(conv2sjis) } private def conv2sjis(org: LiftResponse): LiftResponse = { org match { case x: XhtmlResponse => S.skipXmlHeader = true // *1 val m = x.toResponse // *2 val h = x.headers ::: ("Content-Type", "text/html; charset=Shift_JIS") :: Nil // *3 InMemoryResponse(new String(m.data, "utf-8").getBytes("Shift_JIS"), h, m.cookies, m.code) // *4 case _ => org } }
Message: java.nio.charset.UnmappableCharacterException: Input length = 2 java.nio.charset.CoderResult.throwException(CoderResult.java:261) sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:319) sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158) java.io.InputStreamReader.read(InputStreamReader.java:167) java.io.BufferedReader.fill(BufferedReader.java:136) java.io.BufferedReader.read(BufferedReader.java:157) scala.io.BufferedSource$$anonfun$1$$anonfun$apply$1.apply(BufferedSource.scala:29) scala.io.BufferedSource$$anonfun$1$$anonfun$apply$1.apply(BufferedSource.scala:29) scala.io.Codec.wrap(Codec.scala:65) scala.io.BufferedSource$$anonfun$1.apply(BufferedSource.scala:29) scala.io.BufferedSource$$anonfun$1.apply(BufferedSource.scala:29) scala.collection.Iterator$$anon$13.next(Iterator.scala:145) scala.collection.Iterator$$anon$24.hasNext(Iterator.scala:435) scala.collection.Iterator$$anon$19.hasNext(Iterator.scala:326) scala.io.Source.hasNext(Source.scala:209) net.liftweb.util.PCDataXmlParser$$anonfun$apply$2$$anonfun$apply$4.apply(PCDataMarkupParser.scala:184)
same as BufferedSource.fromInputStream(is, "utf-8", Source.DefaultBufSize)
codec (implicit) a scala.io.Codec specifying behavior (defaults to Codec.default)
def default = apply(Charset.defaultCharset)としているだけ(Charsetはjava.nio.charset.Charset)なので、JVMのシステムプロパティで-Dfile.encodingを設定してやればOKそうです。Mavenでjettyを起動しているので、MVN_OPTSに-Dfile.encoding=utf-8を追加したら問題なく動きました。
import org.specs._ object OAuthSpec extends Specification { import dispatch._ import oauth._ import OAuth._ val svc = :/("term.ie") / "oauth" / "example" // *1, *2 val consumer = Consumer("key", "secret") // *3 "OAuth test host" should { "echo parameters from protected service" in { val h = new Http val request_token = h(svc / "request_token.php" <@ consumer as_token) // *4, *5, *6 val access_token = h(svc / "access_token" <@ (consumer, request_token) as_token) val payload = Map("identité" -> "caché", "identity" -> "hidden", "アイデンティティー" -> "秘密", "pita" -> "-._~") h( svc / "echo_api.php" <<? payload <@ (consumer, access_token) >% { { // *7 _ must_== (payload) } ) } } }
def l2m[T](l: List[T]): Map[T,T] = l match {case x::x2::xs => Map(x -> x2) ++ l2m(xs) case _ => Map()}実行例:
scala> val l = List("a", "1", "b", "2") l: List[java.lang.String] = List(a, 1, b, 2) scala> def l2m[T](l: List[T]): Map[T,T] = l match {case x::x2::xs => Map(x -> x2) ++ l2m(xs) case _ => Map()} l2m: [T](List[T])Map[T,T] scala> l2m(l) res0: Map[java.lang.String,java.lang.String] = Map(a -> 1, b -> 2)