今回は、JSON-RPCでmonacoind操作全般を受け持つユーティリティクラスを作成します。
まずはJSONの扱いですが、2013年のJava EE7の頃に、標準のJSONライブラリが追加されたとか聞いていましたが、あんまりいけてないようです。
POJOにバインドする機能とかもないらしく、自前でマッピングしてあげないといけない?
まぁ実際のところはよく分かりませんが、無難にJacksonを使います。
公式からjackson-coreとjackson-databindをダウンロードしてライブラリに登録しておきます。
リクエスト用とレスポンス用のPOJOをそれぞれ作成し、Jacksonを使ってin、outのstreamと直接変換します。
- リクエスト用クラス
バージョンは1.0固定で良さそうなのでハードコードしてしまいます。
final宣言しておくと、@AllArgsConstructorアノテーションでもjsonrpc抜きのコンストラクタが作成されます。
paramsにはStringやdoubleが入るので、Object型にしておきます。
@AllArgsConstructor @Getter @Setter public class JSONRequest { final String jsonrpc = "1.0"; String id; String method; List<Object> params; }
- レスポンス用クラス
errorは普段nullですが、エラーの場合にはエラー番号やエラー内容がまとめて入ってくるので、MapのvalueはObject型にしておきます。
@Getter @Setter public class JSONResponse { Object result; Map<String, Object> error; String id; }
また、monacoindとの通信で発生するエラーを格納するExceptionクラスも作成しておきます。
- monacoind用Exceptionクラス
monacoindが返してきたエラーメッセージを格納出来るようにしておきます。
@NoArgsConstructor public class CoindException extends Exception { public CoindException(String msg) { super(msg); } }
前準備は整ったので、ここからはCoindUtilクラスを作成し、必要なメソッドを実装していきます。
- リクエスト用文字列を作成するメソッド
ObjectMapperクラスのwriteValueAsStringメソッドで、JSONRequestクラスからJSON文字列を作成します。
public static String createJSONString(String id, String method, List<Object> params) throws JsonProcessingException { JSONRequest jsonRequest = new JSONRequest(id, method, params); return new ObjectMapper().writeValueAsString(jsonRequest); }
- JSON文字列を受け取り、monacoindと通信するメソッド
今回はメインPCから実験用PCにアクセスしているので、monacoin.confのrpcallowipでローカルホスト以外からのアクセスも許可しています。
ID、PWはmonacoin.confに設定したID、PWです。
リクエストはJSON文字列をOutputStreamにPrintWriterでそのまま書き込んで送信します。
何気に、try-with-resource初めて使ったかも。
レスポンスはInputStreamを受け取って、ObjectMapperクラスのreadValueメソッドで、JSONResponseクラスに変換します。
IOExceptionはそのまま上に投げ、monacoindがエラーを返した場合は、先ほど作成したCoindExceptionにメッセージを渡してから上に投げます。
public static JSONResponse connectCoind(String json) throws IOException, CoindException { final String urlStr = "http://192.168.1.15:9359"; final String rpcuser = "rpcuser"; final String rpcpassword = "rpcpassword"; URL url = new URL(urlStr); URLConnection connection = url.openConnection(); Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(rpcuser, rpcpassword.toCharArray()); } }); connection.setDoOutput(true); connection.setRequestProperty("content-type", "text/plain;"); OutputStream out = connection.getOutputStream(); try (PrintWriter pw = new PrintWriter(out)) { pw.print(json); } JSONResponse response; try (InputStream in = connection.getInputStream()) { ObjectMapper mapper = new ObjectMapper(); response = mapper.readValue(in, JSONResponse.class); } catch (IOException e) { try (InputStream es = ((HttpURLConnection) connection).getErrorStream()) { ObjectMapper mapper = new ObjectMapper(); response = mapper.readValue(es, JSONResponse.class); } } Optional<Map<String, Object>> errorOpt = Optional.ofNullable(response.error); if (errorOpt.isPresent()) { throw new CoindException((String) errorOpt.get().get("message")); } return response; }
ここで死ぬほどハマったのが、cURLからだと問題無くJSONでエラーが返ってくる状況で、普通にInputStreamを取り出すと、HTTP response code: 500が返って来たことです。
エラーの内容を見たいのに、500が返ってくるとIOExceptionが投げられてしまい、InputStreamを弄れません。
そして、Google先生との長い対話の末たどり着いたのが、ErrorStreamです。
まず一度IOExceptionをcatchして、URLConnectionをHttpURLConnectionにキャストした上で、getErrorStream()で改めてInputStreamを取得します。
コイツを弄ってやると、ちゃんとmonacoindが返したエラーが読めました。
エラーメッセージが読めた時は感動しましたが、これ設計としてどうなんでしょうか?常識なの?
なんだか釈然としません。
そして最後にせっかくなのでJava8で導入されたOptional使ってみました。
が、ラムダ式内部からExceptionを投げるのがうまくいかなかったので、普通にif文判定。
わざわざOptionalを使った意味があるかは不明ですが、いいんです。
使いたかっただけだから。
次回は実際にmonacoindとやり取りするメソッドの実装です。
0 件のコメント:
コメントを投稿