Jersey を用いた Web API 作成 - その1
新人研修でJavaを学んでいるが、まだ座学がメインなので、勉強がてら手を動かしてみたいと思ったのが動機。
今の所、本家サイトに沿って進めています。
環境構築
最初に、以下がインスコされているか確認します。(括弧内は執筆当時の僕の環境)
プロジェクトの作成
以下のような mvn コマンドを実行します。
mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2 \ -DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false \ -DgroupId=your.domain -DartifactId=your-service-name -Dpackage=your.domain \ -DarchetypeVersion=2.27
-DgroupId
と -DartifactId
と -Dpackage
の右辺は自身のドメインやサービス名に置き換えてください。
上記のコマンドを実行すると以下のような構造のプロジェクトが作成されます。
your-service-name ├── pom.xml └── src ├── main │ └── java │ └── your │ └── domain │ ├── Main.java │ └── MyResource.java └── test └── java └── your └── domain └── MyResourceTest.java
とりあえず動かしてみる
mvn clean test
を実行すると、テスト( your-service-name/test/java/your/domain/MyResourceTest.java
に記述されているテスト)が実行され、ビルドが行われます。
ビルドに成功した場合、以下のようなログが出力されます。
[INFO] Scanning for projects... Downloading from central: https://repo.maven.apache.org/maven2/org/glassfish/jersey/jersey-bom/2.27/jersey-bom-2.27.pom Downloaded from central: https://repo.maven.apache.org/maven2/org/glassfish/jersey/jersey-bom/2.27/jersey-bom-2.27.pom (22 kB at 18 kB/s) [INFO] [INFO] ---------------------< your.domain:your-service-name >---------------------- [INFO] Building your-service-name 1.0-SNAPSHOT [INFO] ----------------------------------[ jar ]----------------------------------- Downloading from central: https://repo.maven.apache.org/maven2/org/codehaus/mojo/exec-maven-plugin/1.2.1/exec-maven-plugin-1.2.1.pom ...(中略)... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running your.domain.MyResourceTest 4 14, 2018 9:20:03 午前 org.glassfish.grizzly.http.server.NetworkListener start 情報: Started listener bound to [localhost:8080] 4 14, 2018 9:20:03 午前 org.glassfish.grizzly.http.server.HttpServer start 情報: [HttpServer] Started. 4 14, 2018 9:20:03 午前 org.glassfish.grizzly.http.server.NetworkListener shutdownNow 情報: Stopped listener bound to [localhost:8080] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.109 sec Results : Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 27.850 s [INFO] Finished at: 2018-04-14T09:20:03+09:00 [INFO] ------------------------------------------------------------------------
上記のようなログが確認できたら mvn exec:java
を実行します。
正常に実行できていれば以下のようなログが出力されるはずです。
[INFO] Scanning for projects... [INFO] [INFO] ---------------------< your.domain:your-service-name >---------------------- [INFO] Building your-service-name 1.0-SNAPSHOT [INFO] ----------------------------------[ jar ]----------------------------------- [INFO] [INFO] >>> exec-maven-plugin:1.2.1:java (default-cli) > validate @ your-service-name >>> [INFO] [INFO] <<< exec-maven-plugin:1.2.1:java (default-cli) < validate @ your-service-name <<< [INFO] [INFO] [INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ your-service-name --- Downloading from central: https://repo.maven.apache.org/maven2/junit/junit/3.8.2/junit-3.8.2.pom Downloaded from central: https://repo.maven.apache.org/maven2/junit/junit/3.8.2/junit-3.8.2.pom (747 B at 699 B/s) Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/maven-plugin-api/2.0/maven-plugin-api-2.0.pom Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/maven-plugin-api/2.0/maven-plugin-api-2.0.pom (601 B at 2.6 kB/s) Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/maven/2.0/maven-2.0.pom Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/maven/2.0/maven-2.0.pom (8.8 kB at 35 kB/s) Downloading from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-exec/1.1/commons-exec-1.1.pom Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-exec/1.1/commons-exec-1.1.pom (11 kB at 43 kB/s) Downloading from central: https://repo.maven.apache.org/maven2/org/codehaus/plexus/plexus-container-default/1.0-alpha-9/plexus-container-default-1.0-alpha-9.jar Downloading from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-exec/1.1/commons-exec-1.1.jar Downloaded from central: https://repo.maven.apache.org/maven2/org/codehaus/plexus/plexus-container-default/1.0-alpha-9/plexus-container-default-1.0-alpha-9.jar (195 kB at 345 kB/s) Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-exec/1.1/commons-exec-1.1.jar (53 kB at 73 kB/s) 4 14, 2018 9:20:19 午前 org.glassfish.grizzly.http.server.NetworkListener start 情報: Started listener bound to [localhost:8080] 4 14, 2018 9:20:19 午前 org.glassfish.grizzly.http.server.HttpServer start 情報: [HttpServer] Started. Jersey app started with WADL available at http://localhost:8080/myapp/application.wadl Hit enter to stop it...
上記のようなログが出てきたら http://localhost:8080/myapp/application.wadl へアクセスしてみましょう。
今回はcURLで叩いてみます。すると、以下のようなXMLが取得できました。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <application xmlns="http://wadl.dev.java.net/2009/02"> <doc xmlns:jersey="http://jersey.java.net/" jersey:generatedBy="Jersey: 2.27 2018-04-10 07:34:57"/> <doc xmlns:jersey="http://jersey.java.net/" jersey:hint="This is simplified WADL with user and core resources only. To get full WADL with extended resources use the query parameter detail. Link: http://localhost:8080/myapp/application.wadl?detail=true"/> <grammars/> <resources base="http://localhost:8080/myapp/"> <resource path="myresource"> <method id="getIt" name="GET"> <response> <representation mediaType="text/plain"/> </response> </method> </resource> </resources> </application>
また、上記のXMLの resources タグの中を見てみると、 http://localhost:8080/myapp/myresource にアクセスできそうな雰囲気がありますね。
http://localhost:8080/myapp/myresource にアクセスしてみると、 Got it!
というプレーンテキストが取得できました。
ひとまず動作を確認することができました。
さいごに
今回は環境構築からプロジェクトの起動までやってみました。
次回はプログラムの中身を見て理解を深めていきたいと思います。
webpack-dev-serverでモジュールの変更が反映されない
webpack-dev-serverのauto-reloadでハマったこと
上の記事と同じくHMRが動かないところで詰まったけど,こちらの記事とは別のやり方で解決できたのでメモとしてまとめようかと思います.
今回,上の記事の解決策ではどうしてダメなのか
上の記事は,最終的には静的なファイル(./index.html
)と webpack-dev-server
で出力されるファイル(./js/build/bundle.js
)を同一ディレクトリ内に配置する(index.html
を./js/build
直下に再配置?)ことで解決しています.
上の記事では,
原因はファイルの場所がよろしくなかったみたい。
serveされる場所に静的ファイルと変更がかかるファイルがないとダメみたいだった。
...中略...
contentBaseを追加して静的ファイルと出力ファイルを同じ階層においたら動いた。
と書かれていますが,今回,以下のようなディレクトリ構成と webpack.config.js
でうまく webpack-dev-server
の auto-reload を機能させたかったので上の記事とは別の解決策を探る方向になりました.
. ├── dist │ ├── css │ ├── index.html -> ./dist/js/index.jsを呼び出す静的なファイル │ └── js │ └── index.js -> Webpackで出力されるファイル └── src ├── component01.js -> ./src/index.jsでimportされるファイル ├── component02.js -> ./src/index.jsでimportされるファイル └── index.js -> Webpackのエントリーファイル
// webpack.config.js const path = require('path'); module.exports = { entry: { path: path.resolve(__dirname, './src/index.js'), }, output: { path: path.resolve(__dirname, './dist/js'), filename: 'index.js', }, devtool: 'source-map', module: { loaders: [{ test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/, query: { presets: ['es2017'] } }] }, resolve: {} };
publicPathを用いる
http://webpack.github.io/docs/webpack-dev-server.html を読むと, webpack.config.js
で output.publicPath
を用いることで解決できそうなのがぼんやりとですがわかりました.
具体的には, webpack.config.js
の output
部分を以下のように書き換えます.
output: { path: path.resolve(__dirname, './dist/js'), publicPath: '/js/', // この行を追加 filename: 'index.js', }
また, ./dist/index.html
から呼び出す際は, <script src="js/index.js"></script>
で呼び出せます.
※ <script src="./js/index.js"></script>
ではなぜか動きません.要調査.
最後に
Chrome の Console を見ると以下のようなログが確認できました.
[WDS] App updated. Recompiling... client?cd17:80 [WDS] App hot update... client?cd17:212 [HMR] Checking for updates on the server... dev-server.js:45 [HMR] Updated modules: log-apply-result.js:20 [HMR] - 11 log-apply-result.js:22 [HMR] - 52 log-apply-result.js:22 [HMR] Consider using the NamedModulesPlugin for module names. log-apply-result.js:28 [HMR] App is up to date. dev-server.js:27
しかし,タブの自動更新が起こらず,手動でタブの更新をしないと変更が反映されません.
こちらも要調査だけど,これで自分の好きなディレクトリ構成で開発ができるようになりました〜.
やっぱり公式ドキュメントはちゃんと読むべきですね...^^;
Moodleの活動モジュールからJavaScriptで非同期通信を行う方法
はじめに
Moodleの活動モジュールにてボタンがクリックされた際に,Moodleサーバへリクエストを飛ばし,DBにアクセスしたかった.
活動モジュールのディレクトリ内にリクエストを受け取るファイルを設けて,そこに向かって Client JS からリクエストを投げても良かったが,セキュリティやらなんやらを考慮した結果, Moodle が用意してくれている機能を用いて上記を実現しようと思った次第.
Moodleやら活動モジュールの説明は割愛.
開発環境
require([‘core/ajax’])
Moodle では JavaScript で Moodleサーバと非同期で通信するために core/ajax (Moodleのルートディレクトリ/lib/amd/src/ajax.js)
というものが用意されている.
そのため,活動モジュール開発者はリクエストパラメーターとコールバック関数だけを書けばいい.
見出しの require(['core/ajax'])
という書き方は,公式のドキュメントに記載されている書き方だが, var ajax = require('core/ajax')
のようにも書ける.
require(['core/ajax'])
を用いる場合,
require(['core/ajax'], function(ajax) { let promises = ajax.call([ methodname: 'hoge', args: {hoge: 'hogehoge', fuga: 'fugafuga'}, methodname: 'fuga', args: {hoge: 'hogehoge', fuga: 'fugafuga'}, ]); promises[0].done(function(res) { console.log(res); }).fail(function(err) { console.log(err); }); promises[1].done(function(res) { console.log(res); }).fail(function(err) { console.log(err); }); });
と書けるため,グローバルを汚染せずに済む.
今回は,ボタンがクリックされた際に上記のような非同期通信を行いたいので, button.addEventListener('click', function(e) { ... });
の … の部分に記述する.
Moodle側のエンドポイント
core/ajax
の実装を見ると, wwwroot(http://localhost:8888/moodle)/lib/ajax/service.php
もしくは service-nologin.php
を叩いていることがわかる.
では, ajax.call
の引数に与えている methodname
や args
は何に用いるのか?
これらは,Moodleが内部でごにょごにょして,最終的に methodname
で指定したメソッドに対して args
を与えて実行している.(厳密には少し違う)
そのため,活動モジュール開発者はサーバ側での処理を記述しておかなければならない.(当たり前っちゃ当たり前)
サーバ側で実行する処理を記述するためにいじるファイルは, Moodleのルートディレクトリ/mod/モジュール名/db/services.php
,Moodleのルートディレクトリ/mod/モジュール名/externallib.php
の2つ.
<?php /* Moodleのルートディレクトリ/mod/モジュール名/db/services.php */ $functions = array( 'hoge' => array( 'classname' => 'mod_hoge_external', 'methodname' => 'methodname', 'classpath' => "mod/モジュール名/externallib.php", 'description' => 'description', 'type' => 'write', 'loginrequired' => false, ), 'fuga' => array( 'classname' => 'mod_fuga_external', 'methodname' => 'methodname', 'classpath' => "mod/モジュール名/externallib.php", 'description' => 'description', 'type' => 'write', 'loginrequired' => false, ), ); $services = array( 'moodle_hogehoge' => array( 'functions' => array (), // 'requiredcapability' => 'some/capability:specified', 'restrictedusers' => 0, 'enabled'=>0, ) );
$functions(連想配列)
のキーは JavaScript 側の methodname
と同じ名前にする必要がある.(JavaScript で $functions['hoge']['methodname']
を指定するとエラーが返ってくるので注意)
<?php /* Moodleのルートディレクトリ/mod/モジュール名/externallib.php */ defined('MOODLE_INTERNAL') || die; class mod_hoge_external extends external_api { public static function methodname_is_allowed_from_ajax() { return true; } public static function methodname_parameters() { return new external_function_parameters( array( 'hoge' => new external_value(PARAM_TEXT, 'hoge'), 'fuga' => new external_value(PARAM_TEXT, 'fuga'), ) ); } public static function methodname($hoge, $fuga) { return array( 'hoge' => $hoge, 'fuga' => $fuga ); } public static function methodname_returns() { return new external_single_structure( array( 'hoge' => new external_value(PARAM_TEXT, 'hoge'), 'fuga' => new external_value(PARAM_TEXT, 'fuga') ), 'description' ); } } /* mod_fuga_externalクラスも同様に定義する */
今回の場合, externallib.php
で最低限定義するクラスやメソッドは,
mod_hoge_external(クラス)
mod_fuga_external(クラス)
methodname_is_allowed_from_ajax(各クラスメソッド)
methodname_parameters(各クラスメソッド)
methodname_returns(各クラスメソッド)
となる.
活動モジュールのインストール
自作の活動モジュールは,
Moodleのルートディレクトリ/mod
直下に自作した活動モジュールを配置- ブラウザからMoodleへアクセス
- 活動モジュールのインストール
- コースなどに活動モジュールを配置
でMoodleにインストールし,コースにモジュールを配置できる.
活動モジュールがインストールされる際, db/services.php
に記述した $functions
は mdl_external_functionsテーブル
に, $services
は mdl_external_servicesテーブル
に保存される.
ひとまず,これでMoodleサーバとの非同期通信ができるようになった.
おわりに
もっと簡単にできると思ってた分しんどかった…(´。_。`)
次は externallib.php
で定義するクラスやメソッドについて少し詳しく書こうかな.
~/.ssh/config のHost情報を使ってgit clone
最近久しぶりに秘密鍵と公開鍵を作って,GitサーバにSSH接続しようとしたら,色々と忘れていたので戒めとして書く.
~/.ssh
LinuxやMacだと各ユーザーのホームディレクトリに .ssh
が隠れているかと思う.
これは ssh-keygen
で作った秘密鍵と公開鍵が置かれるデフォルトのディレクトリである.
~/.ssh/config
~/.ssh/config
は複数サーバーのSSH接続を管理するためのファイル.
~/.ssh/config
をエディタで開き,
Host hoge User git Hostname hoge.com Port 10000 IdentityFile ~/.ssh/hoge_id
のような記述を追加し保存すると,以降, ssh -i ~/.ssh/hoge_id -p 10000 -l git hoge.com
とせずとも, ssh hoge
でSSH接続ができるようになる.
SSHでのgit clone
本題.
Githubであったり,個人のGitサーバなど,リモート上のリポジトリをSSHを用いて git clone
したい時は, git clone hoge:<リポジトリ名>
とすることで,リモート上のリポジトリをcloneできる.(パスワードを求められる場合があるが,これは ssh-keygen
で入力したパスワードを入力すればよい)
もし, ~/.ssh/config
をいじるのに抵抗がある場合は,この記事のように git
コマンドをラップするという方法もある.
ElectronでjQueryを使うための設定
Electron(ver 0.35.4)でjQueryを <script src="jquery.js"></script>
と記述するだけで使えるようにするためには BrowserWindow
のインスタンスを生成する時に以下のように webPreferences > nodeIntegration
を false
にすればいいらしい.
mainWindow = new BrowserWindow({ width: 600, /* ここらへんは */ height: 400, /* てきとうに */ webPreferences: { nodeIntegration: false, }, });
Hashの添字でnilが使えるらしい
ここ最近で一番の驚きだったので.
hoge = {nil => 'hogehoge'} puts hoge[nil]
このプログラムを実行すると hogehoge
と出力される.なぜこのような実装になっているのか.あとで時間がある時にでも調べてみたいと思う.
Titanium側からiOSモジュールに文字列を渡すとなぜか__NSArrayMになる
var Hoge = require('module_id'); Hoge.init({...}); Hoge.fuga('hogefugapiyo');
. . . -(void)fuga:(NSString *)string { NSLog(@"[INFO] %@", NSStringFromClass([string class])); } . . .
上記のようなプログラムがあるとして,fugaメソッドに文字列を渡すと[INFO] __NSArrayM
とログに出力される.
なぜなのか...