ScalatraでWebサービスを作成する

Scalatraは、 Sinatraに近い軽量で高性能なWebフレームワークであり、RubyからScalaに移行するときの生活をより簡単にすることができます。 この記事では、認証機能を備えたシンプルなアプリケーションを作成する例に関するこの興味深いフレームワークについて、マニュアルがロシア語にない場合のギャップを埋めたいと思います。



設置



公式ドキュメントでは、事前に準備されたテンプレートからgiter8を使用してプロジェクトを作成することを提案しています。 ただし、不要なツールを使用せずに実行する場合は、次のようにsbtプロジェクトを簡単に作成できます。



プロジェクト\ plugins.sbt



addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "1.1.0")
      
      





このプラグインを使用すると、特別なsbtコマンドを使用してWebサービスを開始できます。



 $ sbt > container:start
      
      





build.sbt



 val scalatraVersion = "2.4.0-RC2-2" resolvers += "Scalaz Bintray Repo" at "https://dl.bintray.com/scalaz/releases" lazy val root = (project in file(".")).settings( organization := "com.example", name := "scalatra-auth-example", version := "0.1.0-SNAPSHOT", scalaVersion := "2.11.6", scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature"), libraryDependencies ++= Seq( "org.scalatra" %% "scalatra-auth" % scalatraVersion, "org.scalatra" %% "scalatra" % scalatraVersion, "org.scalatra" %% "scalatra-json" % scalatraVersion, "org.scalatra" %% "scalatra-specs2" % scalatraVersion % "test", "org.json4s" %% "json4s-jackson" % "3.3.0.RC2", "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided" ) ).settings(jetty(): _*)
      
      





追加されたライブラリの目的はその名前から理解できます。jsonや認証が必要ない場合は、不要なライブラリを安全に削除できます。



ルーティング



サービスが要求への応答を開始するには、最初に要求に応答するコントローラーを指定する必要があります。 このために次のファイルを作成しましょう。



src \ main \ webapp \ WEB-INF \ web.xml



 <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <servlet> <servlet-name>user</servlet-name> <servlet-class> org.scalatra.example.UserController </servlet-class> </servlet> <servlet-mapping> <servlet-name>user</servlet-name> <url-pattern>/user/*</url-pattern> </servlet-mapping> </web-app>
      
      





xmlを嫌う場合は、この方法で同じことをよりコンパクトに記述できます。



src / main / scala / ScalatraBootstrap.scala



 import org.scalatra.example._ import org.scalatra._ import javax.servlet.ServletContext class ScalatraBootstrap extends LifeCycle { override def init(context: ServletContext) { context.mount(new UserController, "/user") } }
      
      





ここでは、 org.scalatra.example.UserControllerがパスyoursite.example / userで始まるリクエストに応答することを決定しました 。 このファイルの仕組みを見てみましょう。



src \ main \ scala \ org \ scalatra \ example \ UserController.scala



 package org.scalatra.example import org.json4s.{DefaultFormats, Formats} import org.scalatra._ import org.scalatra.json.JacksonJsonSupport import scala.util.{Failure, Success, Try} class UserController extends ScalatraServlet with AuthenticationSupport with JacksonJsonSupport { protected implicit lazy val jsonFormats: Formats = DefaultFormats before() { contentType = formats("json") basicAuth() } get("/") { DB.getAllUsers } get("/:id") { Try { params("id").toInt } match { case Success(id) => DB.getUserById(id) case Failure(ex) => pass() } } }
      
      





このコードをさらに詳しく分析してみましょう。 まず、 ScalatraのすべてのコントローラーはScalatraServletを継承する必要があります。 サーブレットが応答するパスを決定するには、たとえば、要求のタイプに応じて、get、post、put、またはdeleteブロックを追加します。



  get("/") { /*...*/ }
      
      





yoursite.example / userへのリクエストに応答します。 パラメータのいずれかがURLの一部である場合、次のようにパラメータを記述する必要があります。



  get("/:id") { params("id") }
      
      





その結果、 params()メソッドを使用してgetブロック内でidパラメーターを使用できます。 同様に、残りのクエリパラメータを取得できます。 / user / 52のように、同じ名前の複数のパラメーターを渡したい場合は、 Foo = uno&bar = dos&baz = three&foo = anotherfoo (fooパラメーターが2回使用されることに注意してください)、 multiParams()関数を使用して、均一に処理できますパラメーター、例えば:



  multiParams("id") // => Seq("52") multiParams("foo") // => Seq("uno", "anotherfoo") multiParams("unknown") // => an empty Seq
      
      





Pass()メソッドはUserControllerで使用されることに注意してください。 特定のルートでの処理をスキップして、次のルートに進むことができます(この場合、この方法に該当するハンドラーはこれ以上ありません)。 要求の処理を中断し、ユーザーにエラーページを表示する場合は、 halt()メソッドを使用します。このメソッドは、たとえば、リターンコードやエラーテキストなどのさまざまなパラメーターを取ることができます。

フレームワークによって提供される別の可能性は、たとえば次のように書くことで、プリハンドラーとポストハンドラーを設定することです。



  before() { contentType = formats("json") basicAuth() }
      
      





応答のタイプ(この場合はjson )を指定し、ユーザーからの認証を要求できます(認証とjsonの操作については、以下のセクションで説明します)。



ルーティングの詳細については、 公式ドキュメントを参照してください



DBを使用する



前のセクションでは、BDクラスから取得したオブジェクトをコントローラーの応答として使用します。 ただし、 Scalatraには、データベースを操作するための組み込みフレームワークがありません。これに関連して、データベースを操作する模倣を残しました。



src \ main \ scala \ org \ scalatra \ example \ DB.scala



 package org.scalatra.example import org.scalatra.example.models.User object DB { private var users = List( User(1, "scalatra", "scalatra"), User(2, "admin", "admin")) def getAllUsers: List[User] = users def getUserById(id: Int): Option[User] = users.find(_.id == id) def getUserByLogin(login: String): Option[User] = users.find(_.login == login) }
      
      





src \ main \ scala \ org \ scalatra \ example \ models \ User.scala



 package org.scalatra.example.models case class User(id: Int, login:String, password: String)
      
      





ただし、これに問題があるとは思わないでください-公式ドキュメントには、 ScalaMongoDBSquerylRiakなどの最も一般的なデータベースとORMScalatraを友達にする方法が記載されています。



ジョンソン



コントローラーは、 ケースクラスUserを直接返すか、オプション[ユーザー]およびリスト[ユーザー]でさえ返すことに注意してください。 デフォルトでは、 Scalatraは戻り値を文字列に変換し、リクエストへの応答として使用します。たとえば、/ユーザーリクエストへの応答は次のようになります。



 List(User(1,scalatra,scalatra), User(2,admin,admin)).
      
      





サーブレットがjsonとの連携を開始するには、次のことを行う必要があります。



これらの簡単な手順を実行すると、同じ/ユーザーリクエストに対する応答は次のようになります。



 [{"id":1,"login":"scalatra","password":"scalatra"},{"id":2,"login":"admin","password":"admin"}]
      
      







認証



最後に、ユーザー認証などのトピックに触れたいと思います。 これを行うには、 Scentに移植されたWardenフレームワークであるScentryフレームワークを使用することを提案します。これは、Rubyに精通している人々にとっても生活を楽にします。

UserControllerクラスをよく見ると、認証がすでに実装されていることがわかります。 これを行うために、 AuthenticationSupport特性がクラスに追加され、 basicAuth()メソッドがbefore()フィルターで呼び出されます。 AuthenticationSupportの実装を見てください。



src \ main \ scala \ org \ scalatra \ example \ AuthenticationSupport.scala



 package org.scalatra.example import org.scalatra.auth.strategy.{BasicAuthStrategy, BasicAuthSupport} import org.scalatra.auth.{ScentrySupport, ScentryConfig} import org.scalatra.example.models.User import org.scalatra.ScalatraBase import javax.servlet.http.{HttpServletResponse, HttpServletRequest} class OurBasicAuthStrategy(protected override val app: ScalatraBase, realm: String) extends BasicAuthStrategy[User](app, realm) { protected def validate(userName: String, password: String)(implicit request: HttpServletRequest, response: HttpServletResponse): Option[User] = { DB.getUserByLogin(userName).filter(_.password == password) } protected def getUserId(user: User)(implicit request: HttpServletRequest, response: HttpServletResponse): String = user.id.toString } trait AuthenticationSupport extends ScentrySupport[User] with BasicAuthSupport[User] { self: ScalatraBase => val realm = "Scalatra Basic Auth Example" protected def fromSession = { case id: String => DB.getUserById(id.toInt).get } protected def toSession = { case usr: User => usr.id.toString } protected val scentryConfig = new ScentryConfig {}.asInstanceOf[ScentryConfiguration] override protected def configureScentry() = { scentry.unauthenticated { scentry.strategies("Basic").unauthenticated() } } override protected def registerAuthStrategies() = { scentry.register("Basic", app => new OurBasicAuthStrategy(app, realm)) } }
      
      





最初に行うことは、認証戦略( ScentryStrategyインターフェイスを実装するクラス)を定義することです。 この場合、いくつかの標準メソッド実装するBasicAuthStrategy [User]プリフォームを使用しました 。 その後、2つのメソッドを定義する必要があります-validate() 、ログインが成功した場合にSome [User]を返すか、不正なデータの場合にNoneとgetUserId() 、応答ヘッダーにさらに追加するための文字列を返す必要があります。



次に行うことは、 OurBasicAuthStrategyScentrySupportAuthenticationSupportトレイトに組み合わせることです 。これをコントローラーと混合します。 その中で、認証戦略を登録し、(最も簡単な方法で)セッションからユーザーオブジェクトを取得し、逆にそのIDをセッションに追加する方法を実装しました。



その結果、ログインしていないユーザーがUserControllerが処理するページにアクセスする場合、ユーザーは最初にユーザー名とパスワードを入力する必要があります。



おわりに



この記事では、 Scalatraの選択的な機能の一部のみを示しました。 このフレームワークはロシア語圏のコミュニティではあまり人気がありませんが、実装された機能の範囲が広く、開発が容易なため、小さなWebサービスと大規模なサイトの両方を作成するのに非常に有望です。



記事を読んでもまだ質問がある場合は、コメントまたは次の記事で回答する準備ができています。



すべてのソースコードはgithubで入手できます。

素敵な勉強をしてください!



All Articles