Symfoware

Symfowareについての考察blog

jjserver java -jarで実行するときにクラスパスを含めたい

javaスクリプトでapiが記載できるpure javaなプログラムを考えてみました。
jjserver Pure Javaなapi確認用のwebサーバー

せっかくなのでデータベースにも接続したい。


クラスパス問題



javascriptでJDBCドライバを使用し、データベースへ接続する方法もばっちりです。
H2 Databaseでauto_incrementの値をJDBCで取得する

サーバーを起動するとき、

$ java -cp h2-1.4.195.jar -jar jjserver-0.1.jar

と起動すればJDBCドライバが読み込まれるだろうと思っていたのですが、
-jarオプションを指定して起動した場合、jarに含まれるクラスパス指定のみ有効で、
オプションでの指定は無視されるんですよ。


-jarで起動せず、こんな感じでmain関数を含むクラスを指定してやります。


$ java -cp jjserver-0.1.jar:h2-1.4.195.jar com.fc2.blog.symfoware.jjserver.MainProcess




これでjavascriptのファイルからもJDBCドライバを触ることが出来ました。




サンプル



せっかくデータベースに接続できるようになったので、簡単なtodoリストを作ってみます。

・index.html


  1. <!doctype html>
  2. <html lang="ja">
  3.     
  4. <html>
  5. <head>
  6.     <meta charset="UTF-8">
  7.     <title>jjserver</title>
  8.     <meta http-equiv="X-UA-Compatible" content="IE=edge">
  9. </head>
  10. <body>
  11.     <h3>jjserver apiサンプル</h3>
  12.     <!-- ここにタグの内容を反映 -->
  13.     <my-tag></my-tag>
  14. </body>
  15. <!-- ここからRiotプログラム -->
  16. <!-- カスタムタグ -->
  17. <script type="riot/tag">
  18. <my-tag>
  19.     <h3>TODO</h3>
  20.     <ul each={ row in items }>
  21.         <li>{ row.todo }<input type="button" value="削除" onclick={ del }></li>
  22.     </ul>
  23.     <input type="text" ref="post_input" value="">
  24.     <input type="button" value="追加" onclick={ add }>
  25. var self = this
  26. self.items = []
  27. getList(event) {
  28.     // todo一覧取得
  29.     const url = 'http://localhost:8080/api/todo/list'
  30.     fetch(url).then(function(response) {
  31.         return response.json()
  32.     }).then(function(json) {
  33.         self.update({items:json})
  34.     });
  35. }
  36. add(event) {
  37.     // todo 登録
  38.     const obj = {todo: self.refs.post_input.value}
  39.     const method = "POST"
  40.     const body = JSON.stringify(obj)
  41.     const headers = {
  42.         'Accept': 'application/json',
  43.         'Content-Type': 'application/json'
  44.     }
  45.     const url = 'http://localhost:8080/api/todo/add'
  46.     fetch(url, {method, headers, body}).then(function(response) {
  47.         return response.json();
  48.     }).then(function(json) {
  49.         self.update({items:json})
  50.     });
  51. }
  52. del(event) {
  53.     // todo 削除
  54.     const obj = {id: event.item.row.id}
  55.     const method = "POST"
  56.     const body = JSON.stringify(obj)
  57.     const headers = {
  58.         'Accept': 'application/json',
  59.         'Content-Type': 'application/json'
  60.     }
  61.     const url = 'http://localhost:8080/api/todo/del'
  62.     fetch(url, {method, headers, body}).then(function(response) {
  63.         return response.json();
  64.     }).then(function(json) {
  65.         self.update({items:json})
  66.     });
  67. }
  68. this.getList()
  69. </my-tag>
  70. </script>
  71. <!-- マウント -->
  72. <script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.min.js"></script>
  73. <script src="https://cdnjs.cloudflare.com/ajax/libs/riot/3.8.1/riot+compiler.min.js"></script>
  74. <script>
  75. riot.mount('my-tag')
  76. </script>
  77.     
  78. </html>





・api/todo.js


  1. function createStatement() {
  2.     var url = "jdbc:h2:./data/sample"
  3.     var con = java.sql.DriverManager.getConnection(url)
  4.     var stmt = con.createStatement()
  5.     stmt.execute("CREATE TABLE IF NOT EXISTS todo (id INT auto_increment, val TEXT)")
  6.     return stmt
  7. }
  8. function list() {
  9.     stmt = createStatement()
  10.     var rs = stmt.executeQuery("SELECT * FROM todo")
  11.     var result = []
  12.     while(rs.next()){
  13.         result.push({id:rs.getInt('id'), todo:rs.getString('val')})
  14.     }
  15.     return JSON.stringify(result)
  16. }
  17. function add(req) {
  18.     
  19.     stmt = createStatement()
  20.     stmt.execute("INSERT INTO todo (val) VALUES ('" + req.todo + "')")
  21.     return list()
  22. }
  23. function del(req) {
  24.     stmt = createStatement()
  25.     stmt.execute("DELETE FROM todo WHERE id = " + req.id)
  26.     return list()
  27. }




828_01.gif

apiサーバーのサンプル、短くかけていい感じです。

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2018/02/04(日) 22:43:00|
  2. Java
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

jjsスクリプトでH2 Databaseに接続する

以前、jjsスクリプトでMariaDBに接続してみました。
java 8 jjsスクリプト JDBCドライバを使用してデータベースに接続する

これを参考に、jjsスクリプトでh2sqlを使用してみます。
http://www.h2database.com/html/main.html


サンプル



こんな感じになりました。


  1. var url = "jdbc:h2:./data/sample"
  2. var con = java.sql.DriverManager.getConnection(url)
  3. var stmt = con.createStatement()
  4. stmt.execute("CREATE TABLE IF NOT EXISTS t (id INT, val TEXT)")
  5. stmt.execute("TRUNCATE TABLE t")
  6. stmt.execute("INSERT INTO t (id, val) VALUES (1, 'test value 1')")
  7. stmt.execute("INSERT INTO t (id, val) VALUES (2, 'test value 2')")
  8. stmt.execute("INSERT INTO t (id, val) VALUES (3, 'test value 3')")
  9. var rs = stmt.executeQuery("SELECT * FROM t")
  10. while(rs.next()){
  11.     print(rs.getInt('id') + ':' + rs.getString('val'))
  12. }
  13. rs.close()
  14. stmt.close()
  15. con.close()





「-J-Djava.class.path」オプションでダウンロードしておいたjarファイルを指定。
クラスパスに含めてやります。


$ jjs -J-Djava.class.path=h2-1.4.195.jar sample.js
1:test value 1
2:test value 2
3:test value 3




あっさり実行できました。


テーマ:プログラミング - ジャンル:コンピュータ

  1. 2018/02/04(日) 21:28:58|
  2. Java
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

jjserver Pure Javaなapi確認用のwebサーバー

Riotやfetch apiの使い方を調べている時、適当に応答してくれる
web apiが欲しくなりました。

要件として・・・
テスト用なので、サーバー側のapiプログラムは変更後すぐに反映して欲しい。
api側のプログラムは適当なスクリプト言語で簡単に作成したい。


webサーバー



webサーバーは「JLHTTP」を使用することにしました。
https://www.freeutils.net/source/jlhttp/
JLHTTPでお手軽にpure javaなwebサーバーを起動する



apiを作成するスクリプト言語



JavaからJavaScriptのファイルを実行できます。
https://docs.oracle.com/javase/jp/6/technotes/guides/scripting/programmer_guide/

サーバー側のプログラムはJavaScriptで記載することにしました。



パラメーター



postでパラメーターを送信してサーバー側で処理したい。
postパラメーターはjsonに固定することにしました。

送られたjsonな文字列のパースはこのテクニックを使用することにします。
Java 同梱のScriptEngineを利用したJSON文字列のエンコード、デコード




URL



通常はwebサーバーとして振る舞う。
ただし、/api/へのアクセスの場合はJavaScriptのプログラムを実行する。

URLのルールは

/api/[ファイル名]/[関数名]


とする。



上記を踏まえて



こんなプログラムになりました。


  1. package com.fc2.blog.symfoware.jjserver;
  2. import java.io.File;
  3. import java.io.FileReader;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.util.List;
  7. import java.util.ArrayList;
  8. import java.util.Scanner;
  9. import javax.script.Invocable;
  10. import javax.script.ScriptEngine;
  11. import javax.script.ScriptEngineManager;
  12. import jdk.nashorn.api.scripting.ScriptObjectMirror;
  13. import net.freeutils.httpserver.HTTPServer;
  14. import net.freeutils.httpserver.HTTPServer.VirtualHost;
  15. import net.freeutils.httpserver.HTTPServer.ContextHandler;
  16. import net.freeutils.httpserver.HTTPServer.FileContextHandler;
  17. import net.freeutils.httpserver.HTTPServer.Request;
  18. import net.freeutils.httpserver.HTTPServer.Response;
  19. public class MainProcess implements ContextHandler {
  20.     protected final File apiBase;
  21.     
  22.     public MainProcess(File base) throws IOException {
  23.         this.apiBase = new File(base.getCanonicalFile(), "api");
  24.     }
  25.     
  26.     @Override
  27.     public int serve(Request req, Response resp) throws IOException {
  28.         
  29.         resp.getHeaders().add("Content-Type", "application/json");
  30.         
  31.         ScriptEngineManager manager = new ScriptEngineManager();
  32.         ScriptEngine engine = manager.getEngineByName("javascript");
  33.         
  34.         try {
  35.             List<String> paths = parserPath(req.getPath());
  36.             
  37.             
  38.             // bodyはjsonであることを期待
  39.             ScriptObjectMirror json = (ScriptObjectMirror) engine.eval("JSON");
  40.             Object result = json.callMember("parse", streamToString(req.getBody()));
  41.             
  42.             engine.eval(new FileReader(new File(this.apiBase, paths.get(1) + ".js")));
  43.             
  44.             String method = paths.size() < 3 ? "index" : paths.get(2);
  45.             Invocable inv = (Invocable) engine;
  46.             Object res = inv.invokeFunction(method, result);
  47.             
  48.             resp.send(200, res.toString());
  49.             
  50.         } catch(Exception e) {
  51.             e.printStackTrace();
  52.             throw new IOException(e);
  53.         }
  54.         
  55.         
  56.         return 0;
  57.     }
  58.     
  59.     private String streamToString(InputStream is) throws IOException {
  60.         @SuppressWarnings("resource")
  61.         Scanner s = new Scanner(is).useDelimiter("\\A");
  62.         return s.hasNext() ? s.next() : "[]";
  63.     }
  64.     
  65.     private List<String> parserPath(String fullpath) {
  66.         
  67.         List<String> result = new ArrayList<>();
  68.         String[] paths = fullpath.split("/");
  69.         for(String path : paths) {
  70.             if ("".equals(path)) {
  71.                 continue;
  72.             }
  73.             if ("index.html".equals(path)) {
  74.                 continue;
  75.             }
  76.             result.add(path);
  77.         }
  78.         
  79.         return result;
  80.         
  81.     }
  82.     
  83.     
  84.     public static void main(String... args) throws Exception {
  85.         
  86.         String baseDir = args.length < 1 ? "": args[0];
  87.         int port = args.length < 2 ? 8080 : Integer.parseInt(args[1]);
  88.         
  89.         HTTPServer server = new HTTPServer(port);
  90.         
  91.         // デフォルトホスト
  92.         VirtualHost host = server.getVirtualHost(null);
  93.         
  94.         // ディレクトリの表示を許可
  95.         host.setAllowGeneratedIndex(true);
  96.         
  97.         // 「/」アクセスで、カレントディレクトリ表示
  98.         File dir = new File(baseDir);
  99.         host.addContext("/", new FileContextHandler(dir));
  100.         
  101.         // 「/api/」アクセスで、プログラム実行
  102.         host.addContext("/api/", new MainProcess(dir), new String[]{"GET", "POST"});
  103.         
  104.         // サーバー起動
  105.         server.start();
  106.         System.out.println("jjserver is listening on port " + port);
  107.         
  108.     }
  109. }




ソースはここにコミットしてあります。
https://bitbucket.org/symfo/jjserver





使い方



Downloadからjjserver-0.1.jarをダウンロード。

同じ階層に表示用のindex.html
api/sample.jsにプログラムを配置します。

827_01.png


これで、http://localhost:8080/api/sample/[関数名]でアクセスすると、JavaScriptが実行されます。


・index.html


  1. <!doctype html>
  2. <html lang="ja">
  3.     
  4. <html>
  5. <head>
  6.     <meta charset="UTF-8">
  7.     <title>jjserver</title>
  8.     <meta http-equiv="X-UA-Compatible" content="IE=edge">
  9. </head>
  10. <body>
  11.     <h3>jjserver apiサンプル</h3>
  12.     <!-- ここにタグの内容を反映 -->
  13.     <my-tag></my-tag>
  14. </body>
  15. <!-- ここからRiotプログラム -->
  16. <!-- カスタムタグ -->
  17. <script type="riot/tag">
  18. <my-tag>
  19.     <h3>GET</h3>
  20.     <input type="button" value="GET実行" onclick="{ this.getSearch }">
  21.     <table border="1">
  22.         <thead>
  23.             <tr>
  24.                 <td>key</td><td>value</td>
  25.             </tr>
  26.         </thead>
  27.         <tbody>
  28.             <tr><td>get</td><td>{ items.get_echo }</td></tr>
  29.         </tbody>
  30.     </table>
  31.     <h3>POST</h3>
  32.     <input type="button" value="POST実行" onclick="{ this.postSearch }"><br>
  33.     <input type="text" ref="post_input" value="symfoware">
  34.     <table border="1">
  35.         <thead>
  36.             <tr>
  37.                 <td>key</td><td>value</td>
  38.             </tr>
  39.         </thead>
  40.         <tbody>
  41.             <tr><td>post</td><td>{ items.post_echo }</td></tr>
  42.         </tbody>
  43.     </table>
  44. var self = this
  45. self.items = []
  46. getSearch(event) {
  47.     // 単純なget
  48.     const url = 'http://localhost:8080/api/sample/get_echo'
  49.     fetch(url).then(function(response) {
  50.         return response.json()
  51.     }).then(function(json) {
  52.         self.items.get_echo = json.echo
  53.         self.update()
  54.     });
  55. }
  56. postSearch(event) {
  57.     // postでパラメーター送信
  58.     const obj = {name: self.refs.post_input.value}
  59.     const method = "POST"
  60.     const body = JSON.stringify(obj)
  61.     const headers = {
  62.         'Accept': 'application/json',
  63.         'Content-Type': 'application/json'
  64.     }
  65.     const url = 'http://localhost:8080/api/sample/post_echo'
  66.     fetch(url, {method, headers, body}).then(function(response) {
  67.         return response.json();
  68.     }).then(function(json) {
  69.         self.items.post_echo = json.echo
  70.         self.update();
  71.     });
  72. }
  73. </my-tag>
  74. </script>
  75. <!-- マウント -->
  76. <script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.min.js"></script>
  77. <script src="https://cdnjs.cloudflare.com/ajax/libs/riot/3.8.1/riot+compiler.min.js"></script>
  78. <script>
  79. riot.mount('my-tag')
  80. </script>
  81.     
  82. </html>




・api/sample.js


  1. function get_echo() {
  2.     
  3.     var res = {echo:'hello world!'}
  4.     return JSON.stringify(res)
  5. }
  6. function post_echo(req) {
  7.     
  8.     var res = {echo:'hello ' + req.name}
  9.     return JSON.stringify(res);
  10.     
  11. }




引数はpostされたjsonオブジェクト。
returnでクライアントへの応答文字列を返します。



サーバーを起動します。


$ java -jar jjserver-0.1.jar
jjserver is listening on port 8080




http://localhost:8080/を表示。

827_02.png

ボタンを押すと、sample.jsの実行結果が取得できます。

827_03.png

なかなかいい感じです。


テーマ:プログラミング - ジャンル:コンピュータ

  1. 2018/02/04(日) 17:28:38|
  2. Java
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

JLHTTP POST時に501 Not Implemented

Pure JavaなwebサーバーJLHTTPを触ってみました。
JLHTTPでお手軽にpure javaなwebサーバーを起動する

postメソッドを試してみると、501エラーが発生します。

501 Not Implemented


許可するメソッド



FAQを見てみると、デフォルトではGETメソッドが許可されているようです。
http://www.freeutils.net/source/jlhttp/faq

POSTを許可するには、addContextするときの第三引数で指定します。


  1.     public static void main(String... args) throws Exception {
  2.         
  3.         String baseDir = args.length < 1 ? "": args[0];
  4.         int port = args.length < 2 ? 8080 : Integer.parseInt(args[1]);
  5.         
  6.         HTTPServer server = new HTTPServer(port);
  7.         
  8.         // デフォルトホスト
  9.         VirtualHost host = server.getVirtualHost(null);
  10.         
  11.         // ディレクトリの表示を許可
  12.         host.setAllowGeneratedIndex(true);
  13.         
  14.         // 「/」アクセスで、カレントディレクトリ表示
  15.         File dir = new File(baseDir);
  16.         host.addContext("/", new FileContextHandler(dir));
  17.         
  18.         // 「/api/」アクセスで、プログラム実行
  19.         host.addContext("/api/", new MainProcess(dir), new String[]{"GET", "POST"});
  20.         
  21.         // サーバー起動
  22.         server.start();
  23.         System.out.println("jjserver is listening on port " + port);
  24.         
  25.     }


テーマ:プログラミング - ジャンル:コンピュータ

  1. 2018/02/04(日) 16:40:11|
  2. Java
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

JLHTTPでお手軽にpure javaなwebサーバーを起動する

軽量なPure Java HttpServerを探していると、ここを見つけました。
simple HTTP server in Java using only Java SE API


紹介されている中で、「JLHTTP」というのが目に止まったので調べてみます。



JLHTTP



JLHTTP - Java Lightweight HTTP Server (Web Server)

「JLHTTP 2.3」をダウンロードしました。
解凍したフォルダのlibに「jlhttp-2.3.jar」があります。
なんと53.9kb。

webサーバーとして起動するには以下のコマンドを実行。


$ java -jar jlhttp-2.3.jar <directory> [port]




実行したコマンドは以下の通り。


$ java -jar jlhttp-2.3.jar ./ 8080




これでwebサーバーが起動します。

826_01.png


これは軽量&便利。




アプリケーションから起動



Javaアプリケーションからの呼び出しは、ソースファイルやFAQが参考になります。
JLHTTP FAQ

webサーバーとして起動する最低限のソースはこちら。


  1. import java.io.File;
  2. import net.freeutils.httpserver.HTTPServer;
  3. import net.freeutils.httpserver.HTTPServer.VirtualHost;
  4. import net.freeutils.httpserver.HTTPServer.FileContextHandler;
  5. public class MainProcess {
  6.     public static void main(String... args) throws Exception {
  7.         
  8.         HTTPServer server = new HTTPServer(8080);
  9.         
  10.         // デフォルトホスト
  11.         VirtualHost host = server.getVirtualHost(null);
  12.         
  13.         // ディレクトリの表示を許可
  14.         host.setAllowGeneratedIndex(true);
  15.         
  16.         // 「/」アクセスで、カレントディレクトリ表示
  17.         host.addContext("/", new FileContextHandler(new File("")));
  18.         
  19.         // サーバー起動
  20.         server.start();
  21.         
  22.         
  23.     }
  24. }






プログラムの実行



/helloにアクセスしたら、プログラムで文字列を出力してみます。


  1. import java.io.File;
  2. import java.io.IOException;
  3. import net.freeutils.httpserver.HTTPServer;
  4. import net.freeutils.httpserver.HTTPServer.VirtualHost;
  5. import net.freeutils.httpserver.HTTPServer.ContextHandler;
  6. import net.freeutils.httpserver.HTTPServer.FileContextHandler;
  7. import net.freeutils.httpserver.HTTPServer.Request;
  8. import net.freeutils.httpserver.HTTPServer.Response;
  9. public class MainProcess {
  10.     public static void main(String... args) throws Exception {
  11.         
  12.         HTTPServer server = new HTTPServer(8080);
  13.         
  14.         // デフォルトホスト
  15.         VirtualHost host = server.getVirtualHost(null);
  16.         
  17.         // ディレクトリの表示を許可
  18.         host.setAllowGeneratedIndex(true);
  19.         
  20.         // 「/」アクセスで、カレントディレクトリ表示
  21.         host.addContext("/", new FileContextHandler(new File("")));
  22.         
  23.         // 「/hello」アクセスで、プログラム実行
  24.         host.addContext("/hello", new ContextHandler() {
  25.             @Override
  26.             public int serve(Request req, Response resp) throws IOException {
  27.                 resp.getHeaders().add("Content-Type", "text/plain");
  28.                 resp.send(200, "Symfoware");
  29.                 return 0;
  30.             }
  31.         });
  32.         
  33.         // サーバー起動
  34.         server.start();
  35.         
  36.         
  37.     }
  38. }




「/」アクセスでディレクトリ表示。

826_02.png


「/hello」アクセスで文字列を表示。

826_03.png


これは、独自のアプリケーションサーバーが作れそうです。

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2018/02/03(土) 18:55:06|
  2. Java
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
次のページ