- 投稿日:2020-01-03T23:12:42+09:00
Javaの木構造ライブラリ
はじめに
「Google Guavaに無いんだから仕方ない」?まさか。
背景
木構造はたしかにグラフなのだが、だからといってグラフを木構造として使おうとすると気をつけないと木構造ではなくなってしまう。
ライブラリ紹介
さっさと本題に入ろう。
- Search on GitHub: https://github.com/search?l=Java&q=tree&type=Repositories
- Search on Google: https://www.google.com/search?q=java+木構造+ライブラリ
- Search on Bing: https://www.bing.com/search?q=java 木構造 ライブラリ
GitHub: Scalified/tree
URL: https://github.com/Scalified/tree
使いやすさ: ★★★☆☆
充実度: ★★★★☆
二分木、多分木はもちろんのこと、式木があるのがよい。
ただし、実装はあまり選ぶことができない。
加えて、最終コミットが3年前なので、ちゃんとメンテナンスが行われるか心配。GitHub: phishman3579/java-algorithms-implementation
URL: https://github.com/phishman3579/java-algorithms-implementation
使いやすさ: ★★★★★
充実度: ★★★★★★
ライセンス: Apache 2.0
KD木やB+木が有ったりと、全てにおいて良い。ただし、Mavenには上がっていないため、ローカルに組み込む必要がある。まとめ
早くGuavaは木構造を実装してくれ。
- 投稿日:2020-01-03T22:35:11+09:00
JavaとJacksonでJSON その②XSS対策
はじめに
前回の続きとして、XSS対策について触れてみたい。XSSといっても色々あるが、今回JSONの文字列の中にJava Scriptのコードを埋め込まれる場合の問題点にフォーカスしたい。
環境
- Java 1.8
- Tomcat 8.0.53
- Jackson 2.10.1
問題となる状況
前回サーバ側は、URLでGETでアクセスするとJSONを返却するサーブレットを作成したが、今回そのままそれを利用する。そして返却するJSONデータの中にJavaScriptのコードを埋め込んでみる。具体的には以下のJSONを返却するようにした。
{"id":1,"name":"kimisyo","datas":["Programmer","Data Scientist<script>alert('hello!')</script>"]}うまくサニタイジングされないと、
<script>alert('hello!')</script>の部分のJavaScriptのコードが実行されてしまう。まずは、一般的な方法として、AjaxによりサーバのURLにアクセスし、その結果をJSONとして読み込み、中身を表示するJavaScirptを実行してみる。ソース(jspファイル)は以下の通りだ。
clientTest.jsp<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>jQuery Hello World</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script> <script> $.ajax({ url:"http://localhost:8080/servletTest/helloworld", // 通信先のURL type:"GET", // 使用するHTTPメソッド // dataType:"json", // 応答のデータの種類 dataType:"text", // 応答のデータの種類(xml/html/script/json/jsonp/text) timespan:1000 // 通信のタイムアウトの設定(ミリ秒) }).done(function(data,textStatus,jqXHR) { var data2 = JSON.parse(data); alert(data2.datas); // failは、通信に失敗した時に実行される }).fail(function(jqXHR, textStatus, errorThrown ) { alert("error"); // alwaysは、成功/失敗に関わらず実行される }).always(function(){ alert("complete"); }); </script> </head> <body> <div id="test"></div> </body> </html>このjspのURLをブラウザよりたたくと、以下のようにJSONのデータの中身が表示される。JSONに埋め込んだJavaScriptがそのままデータとして読み込まれており、問題のない動作である。
では、次にJSONを返却するサーブレットのURLを直接Edgeでたたいてみる。すると見事に埋め込んだJavaScriptがブラウザにより実行されてしまう。サーバ側の開発に携わる者としては見たくない動作だ。
回避策
ブラウザが"<"と ">"で囲まれた箇所をhtmlのタグとして認識してしまったことが問題であるため、JSONの中の"<", ">" 等の特殊文字をUnicodeエスケープシーケンスに変換してやればよい。
まずは、以下のクラスを作成する。エスケープしたい文字を、CustomCharacterEscapesメソッドに追加していく。CustomCharacterEscapes.javapackage servletTest; import com.fasterxml.jackson.core.SerializableString; import com.fasterxml.jackson.core.io.CharacterEscapes; public class CustomCharacterEscapes extends CharacterEscapes { private final int[] asciiEscapes; public CustomCharacterEscapes() { int[] esc = CharacterEscapes.standardAsciiEscapesForJSON(); esc['"'] = CharacterEscapes.ESCAPE_STANDARD; esc['\''] = CharacterEscapes.ESCAPE_STANDARD; esc['/'] = CharacterEscapes.ESCAPE_STANDARD; esc['\n'] = CharacterEscapes.ESCAPE_STANDARD; esc['>'] = CharacterEscapes.ESCAPE_STANDARD; esc['<'] = CharacterEscapes.ESCAPE_STANDARD; asciiEscapes = esc; } @Override public int[] getEscapeCodesForAscii() { return asciiEscapes; } // no further escaping (beyond ASCII chars) needed: @Override public SerializableString getEscapeSequence(int ch) { return null; } }次に前回のサーブレットのところで一部修正する。
ServletTest.javapackage servletTest; import java.io.IOException; import java.util.List; import java.util.ArrayList; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; // 参考 https://itsakura.com/java-jackson @WebServlet("/helloworld") public class ServletTest extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //Javaオブジェクトに値をセット JsonBean jsonBean = new JsonBean(); jsonBean.setId(1); jsonBean.setName("kimisyo"); List<String> datas = new ArrayList<>(); datas.add("Programmer"); datas.add("Data Scientist<script>alert('hello!')</script>"); jsonBean.setDatas(datas); ObjectMapper mapper = new ObjectMapper(); mapper.getFactory().setCharacterEscapes(new CustomCharacterEscapes()); try { //JavaオブジェクトからJSONに変換 String testJson = mapper.writeValueAsString(jsonBean); //JSONの出力 response.getWriter().write(testJson); } catch (JsonProcessingException e) { e.printStackTrace(); } } }具体的には、
mapper.getFactory().setCharacterEscapes(new CustomCharacterEscapes());を追加している。さあ実験してみよう
上のServletのURLをブラウザより直接たたくとブラウザに以下の通り表示される。Unicodeシーケンスに変換されており、埋め込んだJavaScriptのコードは実行されない。
{"id":1,"name":"kimisyo","datas":["Programmer","Data Scientist\u003Cscript\u003Ealert(\u0027hello!\u0027)\u003C\u002Fscript\u003E"]}では、Unicodeシーケンスに変換しても、JSONを受け取ったJavasScirpt側では正しくJSONデータとして解釈されるだろうか。
このJSONをJavaScriptでAjaxでアクセスして表示させると、変換しない場合と同様に、"<"や">"がJSONのデータとして読み込まれた結果が表示され、正しく解釈されたことが分かる。おわりに
次は HTMLにJSONを埋め込んでJavaScript から利用するケースの問題点について取り扱ってみたい。
参考
- 投稿日:2020-01-03T22:33:31+09:00
JDKのあれこれ
- 投稿日:2020-01-03T20:04:27+09:00
Spring 案件ソルーション 3
Spring画面系をどう乗り切るか(技術面)その3
CSS/Jqueryを試してみる。
趣旨
とんがった案件でなくてもSpring画面系で必要な技術 HTML CSS jQuery Bootstrap
が出てくるのが当たり前になっている状況がある。今回は公開しているページを改良しながら
電卓を作ってみることによりCSS/Jqueryの練習をしてみようと思う。参考にしたページ
https://codeforfun.jp/jquery-how-to-create-a-simple-calculator/
修正内容
・ 4則計算記号を変更した。
→ それに伴って正規表現を使用した。
・ gridを使用するようにした。モジュール一覧
・calc.html
・calc.css学習用アプリの画像貼り付け
プログラム部分
<script> $(function(){ $('.button').click(function(){ var pushed = $(this).text(); var inputLabel = $('#boxinputLabel'); var h1Text = inputLabel.text(); h1Text = h1Text.replace("×", "*"); h1Text = h1Text.replace("÷", "/"); if (pushed == '=') { // 計算 inputLabel.text(eval(h1Text)); } else if (pushed == 'AC') { // 全てクリア inputLabel.text('0'); } else { alert(inputLabel.text()) console.log(inputLabel.text()); if (inputLabel.text() == '0') { inputLabel.text(pushed); } else { inputLabel.text(inputLabel.text() + pushed); } } }); });<style> * { font-family: 'Inconsolata', monospace; color: #555; } body { background-color: white; } body > * { margin: 3px; padding: 10px; } .container { margin: auto; padding:auto; display: grid; grid-template-columns: 40px 40px 40px 40px; grid-template-rows: 80px 40px 40px 40px 40px 40px; width: 220px; background-color: #E0DEDA; grid-gap: 20px; } .container > * { align : center; } #boxinputLabel{ grid-column: 1 / span 4; grid-row-start: 1; grid-row-end: 2; background-color: #DFE9E9; padding-left: 1rem; padding-right: 1rem; } #boxAC { grid-column: 1 / span 3; grid-row-start: 2; grid-row-end: 2; background-color: white; padding-left: 1rem; padding-right: 1rem; } #box÷ { width:10%; grid-column: 4/ span 1; grid-row:2; background-color: white; padding-left: 1rem; padding-right: 1rem; } #box7 { width:10%; grid-column: 1/ 4; grid-row:3; background-color: white; padding-left: 1rem; padding-right: 1rem; } #box8 { width:10%; grid-column: 2/ 4; grid-row:3; background-color: white; padding-left: 1rem; padding-right: 1rem; } #box9 { width:10%; grid-column: 3/ 4; grid-row:3; background-color: white; padding-left: 1rem; padding-right: 1rem; } #box× { width:10%; grid-column: 4/ 4; grid-row:3; background-color: white; padding-left: 1rem; padding-right: 1rem; } #box4{ width:10%; grid-column: 1/ 4; grid-row:4; background-color: white; grid-gap: 10px; padding-left: 1rem; padding-right: 1rem; } #box5{ width:10%; grid-column: 2/ 4; grid-row:4; background-color: white; grid-gap: 10px; padding-left: 1rem; padding-right: 1rem; } #box6{ width:10%; grid-column: 3/ 4; grid-row:4; background-color: white; padding-left: 1rem; padding-right: 1rem; } #boxMinus{ width:10%; grid-column: 4/ 4; grid-row:4; background-color: white; padding-left: 1rem; padding-right: 1rem; } #box1{ width:10%; grid-column: 1/ 4; grid-row:5; background-color: white; padding-left: 1rem; padding-right: 1rem; } #box2{ width:10%; grid-column: 2/ 4; grid-row:5; background-color: white; padding-left: 1rem; padding-right: 1rem; } #box3{ width:10%; grid-column: 3/ 4; grid-row:5; background-color: white; padding-left: 1rem; padding-right: 1rem; } #boxPlus{ width:10%; grid-column: 4 / 4; grid-row:5; background-color: white; padding-left: 1rem; padding-right: 1rem; } #box0{ grid-column:1 / span 2; grid-row:6; background-color: white; padding-left: 1rem; padding-right: 1rem; } #boxTen{ width:10%; grid-column: 3/ 4; grid-row:6; background-color: white; padding-left: 1rem; padding-right: 1rem; } #boxEqual{ width:10%; grid-column: 4/ 4; grid-row:6; background-color: white; grid-gap: 10px; padding-left: 1rem; padding-right: 1rem; }IBMCloud上に乗せたもの。
Flaskアプリ上に先ほどのアプリを乗せている。
試せる。https://flask-sample-jquery.mybluemix.net/
参考 URL
https://www.ibm.com/cloud/blog/simple-hello-world-python-app-using-flask今後
気になっているところ
→ Form+Jquery
- 投稿日:2020-01-03T19:45:34+09:00
Java8 ラムダ式の filter() で使用する条件をパラメータで渡す
ラムダ式は変数に代入して再利用することができますが、そのフィルタ条件などは固定されています。
Java8 ラムダ式を変数に代入して再利用するそのため、そのフィルタ条件を変えたい場合、別々の式を定義する必要があるかもしれません。
例えば、
”文字列の長さが5の文字列のみ”をフィルタするラムダ式と、
”文字列の長さが8の文字列のみ”をフィルタするラムダ式が欲しい場合、以下のように2つの式を定義する方法が考えられます。lengthFiveOrEight// 文字列の長さが5 final Predicate<String> lengthEqualFive = name -> name.length() == 5; // 文字列の長さが8 final Predicate<String> lengthEqualEight = name -> name.length() == 8;条件が常に固定であれば問題ありませんが、そのアプリケーションが柔軟に条件の変更をしたい場合、その数だけ式を定義することになるかもしれません。
この条件(この例では5か8)をパラメータとして受け取ることが出来れば、このような重複を避けることが出来ます。
例えば、以下のようなメソッドを定義する方法が考えられます。lengthEqualWithPredicate<String> lengthEqualWith(final Integer expectsLength) { return name -> name.length == expectsLength; }単にラムダ式を変数に代入する代わりに、ラムダ式を返すメソッドに引数を渡し、その引数をラムダ式に適用するような形です。
lengthEqualWith() を呼び出す時に、expectsLength をパラメータとして渡してあげることで、柔軟に条件を変更することが可能となります。以下のリストを使ってフィルタしてみます。
final List<String> months = Arrays.asList("January", "February", "March", "April", "May", "June", "July", "Augast", "September", "October", "November", "December");以下のように lengthEqualWith(N) を適用しています。
System.out.println("Length is 5"); List<String> result1 = months.stream().filter(lengthEqualWith(5)).collect(Collectors.toList()); result1.forEach(System.out::println); System.out.println("Length is 8"); List<String> result2 = months.stream().filter(lengthEqualWith(8)).collect(Collectors.toList()); result2.forEach(System.out::println);以下のような(期待通りの)結果が得られます。
Length is 5 March April Length is 8 February November Decemberこのラムダ式は expectsLength 変数をスコープ内で探しますが、このスコープのことを静的スコープ(または構文スコープ)と呼ばれるようです。
expectsLength 変数がスコープ内に存在(キャッシュ)していれば、ラムダ式はその値を使います。今回の例では、lengthEqualWith() メソッド内部がその静的スコープになります。
ラムダ式は final のローカル変数にしかアクセスできないため、expectsLength 変数は final である必要があります。たとえ、final と宣言されていなくとも、final である条件が満たされていれば(つまり、その変数が初期化されており、不変であれば)動作するようです。
ただ、Java としてはそれらの条件が満たされているかを評価する必要がある(コストがかかる)ため、こういった値をキャッシュするのとしないのでは、パフォーマンスに若干の違いが出るかもしれません。
- 投稿日:2020-01-03T19:07:05+09:00
Java/SpringでreCAPTCHA v3を実装する
はじめに
ググってもJava向けの記事がなかったのと、公式サイトは若干わかりづらかったのでまとめてみました。
公式リンク
参考リンク
- お問い合わせフォームにreCAPTCHA v3を導入してみました!
- 基本的な実装が簡潔にまとまっている
- reCAPTCHA v3 入れてみた
- エラー分岐なども考慮してまとまっている
登録方法
まずサービスを利用するために登録を行います。
お問い合わせフォームにreCAPTCHA v3を導入してみました!
⇒「reCAPTCHA v3の登録方法」を参照実装方法
登録が済めば後は実装するだけです。
クライアント側
HTML
formタグ内に以下を追記。
JavaScriptによって取得したtokenをこのvalueに設定して、submitします。
Ajaxを使うなら、直接tokenをRequestパラメータに指定することによりこの記述は不要です。hoge.html<input type="hidden" name="recaptchaResponse" id="recaptchaResponse">script読み込み部分に以下を追記。
自サイトでreCAPTCHAを使えるようにします。
(サイトキー)には登録時に取得したサイトキーを代入します。hoge.html<script src="'https://www.google.com/recaptcha/api.js?render=' + (サイトキー)"></script>JavaScript
form submit時に実行される関数を以下のように実装。
actionに設定した値(以下では'contact_form')はreCAPTCHAからのResponseに格納されるので、サイト上のどこで実行されたかなどを設定しておくと分析とかに使えるかも。hoge.js$form = $(/* formを取得 */); $button = $(/* form内のsubmitボタンを取得 */); $button.click(function() { // reCAPTCHA v3用 grecaptcha.ready(function () { grecaptcha.execute((サイトキー), {action: 'contact_form'}).then(function(token) { $('#recaptcha-response').val(token); // reCAPTCHA実装前に関数内に記述していた処理はここに記述 $form.submit(); }); }); });バックエンド側
Controller
POST先のメソッドを以下のように実装。実際にはロジック部分はService層に切り出すこと。
BOT判定(if文の部分)については次のModelセクションで解説します。HogeController.java@RequestMapping(value = "/hoge", method = RequestMethod.POST) public String hogePost( @Valid @ModelAttribute("hogeForm") HogeForm hogeForm, BindingResult bindingResult, HttpServletRequest request, SitePreference sitePreference, Model model) { String url = "https://www.google.com/recaptcha/api/siteverify?secret=" + (シークレットキー) + "&response=" + hogeForm.getRecaptchaResponse; RestTemplate restTemplate = new RestTemplate(); RecaptchaResult result = restTemplate.getForObject(url, RecaptchaResult.class); log.info("reCAPTCHA result: " + result.toString()); if (result.isSuccess()) { if ( 0.5 <= result.getScore()) { // BOTと判定されなかった場合の処理を記述 } else { // BOTと判定された場合の処理を記述 } } else { // reCAPTCHAに接続失敗した場合の処理を記述 } }Model
reCAPTCHA APIのResponseを格納するModelを以下のように実装。
getterとsetterを自動生成するlombokを使用しています。
Responseの内容は公式を参照してください。重要なこと
successプロパティはAPIの接続の成功・失敗を表すものであり、BOTかどうかの判定ではないことに注意する。
reCAPTCHAでは返却されたscoreプロパティ(0.0~1.0)を基にBOTかどうかを自プログラムで判断する。scoreが1に近いほどBOTの可能性が低く、0に近いほどBOTの可能性が高い。
Google公式 Interpreting the score曰く、最初の閾値は0.5でいいとのこと。RecaptchaResult.javapackage com.croooober.v1.cr_www.model.api; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; @Data @JsonIgnoreProperties(ignoreUnknown = true) public class RecaptchaResult { private boolean success; private String challenge_ts; private String hostname; private float score; private String action; public RecaptchaResult() { } }また、Formオブジェクトにプロパティを追加。
HogeForm.javaprivate String recaptchaResponse;うまく実装できていれば、仕込んどいたlog出力で結果を確認できると思います。
感想
最初はAPIに投げればBOTかどうか判定してくれるものと思っていました。
実際にはscoreを基に自プログラムで判定する必要があって少しだけ混乱しました。
- 投稿日:2020-01-03T17:44:40+09:00
Javaでhttps接続確認する
https接続確認をブラウザからではなく、Javaから行います。
環境はJava8を利用しています。他にはライブラリは利用していません。ソース
package ssltest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import java.net.UnknownHostException; import java.security.cert.Certificate; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLPeerUnverifiedException; public class Connect { public static void main(String[] args) { try { URL url = new URL("https://google.co.jp/"); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.connect(); dispEnv(); dispContents(url); dispCerts(conn); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public static void dispEnv() { try { System.out.print("オペレーティングシステム名(os.name):"); System.out.println(System.getProperty("os.name")); System.out.print("Javaバージョン(java.version):"); System.out.println(System.getProperty("java.version")); InetAddress ia = InetAddress.getLocalHost(); String ip = ia.getHostAddress(); String hostname = ia.getHostName(); System.out.println("IPアドレス(getLocalHost):" + ip); System.out.println("ホスト名(hostName):" + hostname); } catch (UnknownHostException e) { e.printStackTrace(); } } public static void dispCerts(HttpsURLConnection conn) { System.out.println("----サーバの証明書チェーンを表示----"); try { Certificate[] certs = conn.getServerCertificates(); for (Certificate cert : certs) { System.out.println(cert); } } catch (SSLPeerUnverifiedException e) { e.printStackTrace(); } } public static void dispContents(URL url) { System.out.println("----コンテンツを表示----"); try { InputStream strm = url.openStream(); InputStreamReader in = new InputStreamReader(strm); BufferedReader inb = new BufferedReader(in); String line; while ((line = inb.readLine()) != null) { System.out.println(line); } inb.close(); in.close(); strm.close(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }実行結果
一部内容を伏せているのと省略をしています。
オペレーティングシステム名(os.name):Windows 10 Javaバージョン(java.version):1.8.0_74 IPアドレス(getLocalHost):192.168.XXX.XXX ホスト名(hostName):DESKTOP-XXXXX ----コンテンツを表示---- <!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="ja"><head> ・・・ </body></html> ----サーバの証明書チェーンを表示---- [ [ Version: V3 Subject: CN=*.google.co.jp, O=Google LLC, L=Mountain View, ST=California, C=US Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11 ・・・エラーの場合
記事を書くときに参考にしたサイト様ではエラーが発生してしまいました。
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1949) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1509) at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216) at sun.security.ssl.Handshaker.processLoop(Handshaker.java:979) at sun.security.ssl.Handshaker.process_record(Handshaker.java:914) at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1062) at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375) at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403) at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387) at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559) at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185) at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:153) at ssltest.Connect.main(Connect.java:22) Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:387) at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292) at sun.security.validator.Validator.validate(Validator.java:260) at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324) at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229) at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1491) ... 11 more Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141) at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126) at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280) at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382) ... 17 more正常時との差異
どうやら、基本制限に△が付いているとなるようです。
分かったことがあれば追記します。
実行jarファイル
気軽に実行できるようにjarファイルを用意しました。下記jarファイルをダウンロードして、動かしたいサーバに配置した後にjavaのパスを通した状態で引数にURLを指定して利用してください。
https://github.com/koni1212/httpsConnect/raw/master/jarfile/httpsConnect.jar
- 投稿日:2020-01-03T15:58:37+09:00
【2020年1月】令和だし本格的にVSCodeのRemote Containerで、爆速の"開発コンテナ"始めよう
VSCode の Remote Conainer で"開発環境+プロジェクト全部入りのコンテナ"から開発をスタートダッシュをキメませんかッ!?
開発でVS Code の Remote Conainer使っていますか?単に既存のコンテナに入るだけなら Remote SSH でも構いませんが、"ローカル開発環境の一部"として、いやむしろローカルの開発環境=Remote Containerとして、ビンビンにRemote Container使っていきましょう。令和だし!
(すでに2年だけどね・・・?)特にMacを使っていると最初からPythonやらPHPやらRubyやらが入ってしまっているので開発環境があるのですが、これらは割とmacOSのエコシステムに組み込まれているので不要にパッケージの追加削除、できないのですよ。
brewとか意外とあっさり壊れますしね・・・。特にバージョンアップなんてもってのほかです。全然、余裕でおかしくなります。
そんなわけでMacに入っているPythonやRubyでプログラミングをバリバリしていると・・・ふと、後戻りできない状況になったりするわけです。
そんなことにならないためにも、"Remote Containerでの開発"に入門しましょう~!"Dev Container" 機能のご紹介
Remote Container の本家サイトと本家Githubで"Try a dev container"と言う項目とリポジトリがあるの、ご存知でしょうか?
"dev container"は開発環境入りコンテナが付属したプロジェクトのサンプルで、以下の各言語向けにdev-containerのサンプルが用意されています。
- Node.js, Javascript
- Python
- Go
- Java
- .Net Core
- PHP
- Rust
- C++
例えば、node.js、Javascript用のサンプルは以下のようなツリーになっております。
% git clone https://github.com/Microsoft/vscode-remote-try-node nodejs-dev-sample % cd nodejs-dev-sample % tree -a -I ".git" . ├── .devcontainer │ ├── Dockerfile │ └── devcontainer.json ├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .vscode │ └── launch.json ├── LICENSE ├── README.md ├── package.json ├── server.js └── yarn.lock
treeコマンドで.gitだけ除外して全て表示すると上記のようになります。
ここで気になるのが・・・.devcontainerですよね?!
中身のDockerfileとdevcontainer.jsonは以下のようになっております。#------------------------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. #------------------------------------------------------------------------------------------------------------- FROM node:10 # The node image includes a non-root user with sudo access. Use the "remoteUser" # property in devcontainer.json to use it. On Linux, the container user's GID/UIDs # will be updated to match your local UID/GID (when using the dockerFile property). # See https://aka.ms/vscode-remote/containers/non-root-user for details. ARG USERNAME=node ARG USER_UID=1000 ARG USER_GID=$USER_UID # Avoid warnings by switching to noninteractive ENV DEBIAN_FRONTEND=noninteractive # Configure apt and install packages RUN apt-get update \ && apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \ # # Verify git and needed tools are installed && apt-get -y install git iproute2 procps \ # # Remove outdated yarn from /opt and install via package # so it can be easily updated via apt-get upgrade yarn && rm -rf /opt/yarn-* \ && rm -f /usr/local/bin/yarn \ && rm -f /usr/local/bin/yarnpkg \ && apt-get install -y curl apt-transport-https lsb-release \ && curl -sS https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/pubkey.gpg | apt-key add - 2>/dev/null \ && echo "deb https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ && apt-get update \ && apt-get -y install --no-install-recommends yarn \ # # Install eslint globally && npm install -g eslint \ # # [Optional] Update a non-root user to UID/GID if needed. && if [ "$USER_GID" != "1000" ] || [ "$USER_UID" != "1000" ]; then \ groupmod --gid $USER_GID $USERNAME \ && usermod --uid $USER_UID --gid $USER_GID $USERNAME \ && chown -R $USER_UID:$USER_GID /home/$USERNAME; \ fi \ # [Optional] Add add sudo support for non-root user && apt-get install -y sudo \ && echo node ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ && chmod 0440 /etc/sudoers.d/$USERNAME \ # # Clean up && apt-get autoremove -y \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/* # Switch back to dialog for any ad-hoc use of apt-get ENV DEBIAN_FRONTEND=dialog上記の解説は後に回して、続いて
devcontainer.jsonの中身は・・・devcontainer.json{ "name": "Node.js Sample", "dockerFile": "Dockerfile", // Use 'appPort' to create a container with published ports. If the port isn't working, be sure // your server accepts connections from all interfaces (0.0.0.0 or '*'), not just localhost. "appPort": [3000], // Comment out the next line to run as root instead. "remoteUser": "node", // Use 'settings' to set *default* container specific settings.json values on container create. // You can edit these settings after create using File > Preferences > Settings > Remote. "settings": { "terminal.integrated.shell.linux": "/bin/bash" }, // Specifies a command that should be run after the container has been created. "postCreateCommand": "yarn install", // Add the IDs of extensions you want installed when the container is created in the array below. "extensions": [ "dbaeumer.vscode-eslint" ] }と、コンテナの設定とvscodeのsettings.jsonの混ざったような形式のファイルとなっております。
この設定がなんなのかはだいたい、想像がつくかと思いますが、このフォルダをVSCodeで開くと何が起こるのでしょうか…試してみましょう!
その前に、Dockerデーモンが立ち上がってない方は事前にDockerデーモンを立ち上げてください。WindowsやMacの方はDocker for Desktopを起動しておいてください。Dockerが起動したらVSCodeを立ち上げて
nodejs-dev-sampleを開いてみます。
すると・・・
dev container configurationが見つかったからコンテナで開くか?と言う問い合わせが出ました!
そしてReopen in Containerをクリックすると・・・
しばらく時間が経って…開きました!左下のステータスバーがグリーンに変わってRemote接続中であることと、"Dev Container: Node.js Sample"の文字が眩しいですね!
そうなんです。.devcontainerフォルダとdevcontainer.jsonの設定と然るべきDockerfileが揃っていればプロジェクトフォルダ内のファイルを丸ごとマウントしたコンテナの自動生成とプロジェクトをVSCodeで開くのを勝手にやってくれるのです!また、ここでのDockerfileの特筆するべき点はdockerに問題の"root"ユーザー問題を宜しく解決してくれている点です。
"コンテナの中で開発する"のは聞こえはいいですが、大抵のイメージはそのまま実行するとユーザーが"root"になってしまうのが多いので、コンテナの中で更新されるファイルのオーナーが"root"になってしまってウザい問題がありました。自前でDockerfile書けばなんとでもなるのですがいちいち書いてられないし、いちいちDockerfile書くくらいなら開発環境汚れても別にいいじゃん会社のPCだし、みたいなことになってますよね!?
このDockerfileでは丁寧にそこのところをサポートしてくれているので、rootユーザー問題が無事に解決されています。ついでにコンテナの管理も VSCode からできるので…
このようにどのプロジェクトでどのコンテナ使っているかは、VSCodeから管理できます。
特にDockerではどのフォルダのDockerfileで立ち上げたコンテナかがわからない(と言うかイメージを作ってコンテナ起動するのでどこのフォルダのDockerfileで作成したイメージかどうかは本来は関係ないハズ)のでこれは便利です。どうやって使うのか?
まずは本家リポジトリのご紹介をしておきましょう。
リポジトリ名からどの言語向けのサンプルプロジェクトかわかると思います。
- microsoft/vscode-remote-try-python
- microsoft/vscode-remote-try-node
- microsoft/vscode-remote-try-go
- microsoft/vscode-remote-try-java
- microsoft/vscode-remote-try-cpp
- microsoft/vscode-remote-try-dotnetcore
- microsoft/vscode-remote-try-rust
- microsoft/vscode-remote-try-php
実際には
.devcontainerフォルダとdevcontainer.jsonの設定と使用されるDockerfileの3つが揃っていれば自動的にやってくれますので、これらのファイルのみを本家リポジトリからコピペで作成するのもアリです。
とは言え毎度コピペするのも面倒なので、以下のように手順を整えてしまいましょう。1. 雛形としてクローン
上記のリポジトリを以下のように"プロジェクトの名前"でクローンしてきます。
$ git clone https://github.com/microsoft/vscode-remote-try-php my-first-phpリポジトリ名の後ろの引数が作成されるフォルダ名で、別名でクローンするのが第1のミソです。
2. 履歴の削除と再作成
当然、cloneしたばかりでは本家リポジトリのコミット履歴を全て含んでおります。
このまま再利用しても全然、良いのですが・・・大抵の場合は気になるでしょう。
そこで.git以下をざっくり削除します。$ rm -rf .gitこれで過去のコミット履歴は綺麗さっぱり忘れました。
ついでに不要なファイルは削除しておきましょう。
なんなら.devcontainer以外は不要です。.vscodeはお好みにお任せいたします。で、お掃除が完了したところで新規のリポジトリとして初期化します。
$ git init ...これで新たな開発コンテナプロジェクトとして第一歩が始まりました!
3. Dockerfile のカスタマイズ
実際の案件ではDBやAngular、Aws、Firebase、AzureなどのCLIなどなどなどを入れる必要があったりするのでDockerfileがそのまま使えることはほぼないです。
よってDockerfileに予め必要なツールをインストールするコマンドを入れておきます。Dockerfileの記述に関してはここでは割愛させていただきます。。。
4. devcontainer.json のカスタマイズ
devcontainer.jsonではプロジェクト名や転送するポート、必要なVSCodeのプラグインなどの設定ができ、とても重要なファイルです。
設定値のリストは以下の箇所にあります。サンプルリポジトリの中で使われている設定をいくつか抜き出してみますと・・・
- settings … これはコンテナの中で使用される VSCodeのsetting.jsonの設定値となります。
- appPort … 転送するポートです
- postCreateCommand … コンテナ作成後に実行されるコマンド
- extensions … リモート側にインストールされるVSCodeプラグイン
あたりが重要なカスタマイズするポイントでしょうか。
また、リファレンスによると
docker-compose.ymlも利用可能な模様です。これらを設定後に、VSCodeからフォルダを開けば、即座に開発環境込みのプロジェクトのスタートです!
まとめ
VScode Remoteコンテナの機能の入門編を書いてみるにあたって、公式ドキュメントを見直しましたが…ボリュームが半端ないですね。
これをどこまで深堀りするべきか悩みましたが、入門編ということでほとんど触れないようにいたしました(笑)
本文中に飛び飛びでリンクを貼っていますが、Remote Containerの公式ヘルプは驚愕の1ページです。これだけで本書けそうなボリュームですよ・・・
というところで、今回はここまでといたします!
- 投稿日:2020-01-03T12:19:06+09:00
Javaで作る自動販売機(ドメイン駆動的な?)
経緯
shiracamus様からのアドバイスを元に、DDD(ドメイン駆動)的に書き直しました。
その際、ロジックの見直しも一部行い、よりシンプルになるように致しました。処理フロー
・インスタンス生成
・商品リスト初期化(在庫補充)
・商品一覧表示(陳列商品の表示)
・商品選択(ユーザによる入力)
・入金(ユーザによる入力)
・販売(課金処理)
・おつり(返金処理)動作イメージ
ソースコード
Main.javapackage vm; import java.util.Scanner; public class Main { public static void main(String[] args) { // インスタンス生成 Store venderMachine = new Store(); @SuppressWarnings("resource") Scanner scanner = new Scanner(System.in); // 商品リスト初期化 Product water = new Product("水", 80); Product coke = new Product("コーラ", 100); Product orangejuice = new Product("オレンジジュース", 120); venderMachine.addStock(water); venderMachine.addStock(coke); venderMachine.addStock(orangejuice); // 商品一覧表示 System.out.println("≪販売中の商品です≫"); venderMachine.showProductList(); // 商品選択 Product purchase = null; String purchaseSelectMessage = "購入する商品名を入力してください。"; int purchaseSelectCount = 0; do { if(purchaseSelectCount > 0) { purchaseSelectMessage = "その商品の取り扱いはございません。販売中の商品からお選び下さい。"; } System.out.println(); System.out.println(purchaseSelectMessage); purchase = venderMachine.inStocker(scanner.next()); purchaseSelectCount++; } while(!venderMachine.isExistsStock(purchase)); // 入金 String depositMessage = "入金して下さい。"; int depositCount = 0; do { if(depositCount > 0) { depositMessage = "入金額が不足しています。不足分を追加してください。"; } System.out.println(); System.out.println(depositMessage); venderMachine.addDeposit(scanner.nextInt()); depositCount++; } while(!venderMachine.isEnoughDeposit(purchase)); // 販売 System.out.println(); venderMachine.sale(purchase); // おつり System.out.println(); venderMachine.change(); } }Store.javapackage vm; public class Store { private Stocker stocker; private Casher casher; Store() { stocker = new Stocker(); casher = new Casher(); } void addDeposit(int deposit) { casher.addDeposit(deposit); } void addStock(Product product) { stocker.add(product); } void change() { System.out.println("おつりは、" + casher.change() + "円です。"); } void showProductList() { stocker.showProductList(); } boolean isExistsStock(Product product) { return stocker.isExistsProduct(product); } boolean isEnoughDeposit(Product purchase) { return casher.isEnoughDeposit(purchase); } Product inStocker(String productName) { return stocker.getProduct(productName); } void sale(Product purchase) { casher.charge(purchase); System.out.println(purchase.name() + "です!"); System.out.println("ご購入ありがとうございました。"); } }Casher.javapackage vm; public class Casher { private int deposit; private int change; Casher(){ deposit = 0; change = 0; } void addDeposit(int deposit) { this.deposit += deposit; } void charge(Product purchase) { change = deposit - purchase.price(); } int change() { return change; } public boolean isEnoughDeposit(Product product) { if(deposit >= product.price()) { return true; } return false; } }Stocker.javapackage vm; import java.util.ArrayList; import java.util.List; public class Stocker { private List<Product> productList; Stocker() { productList = new ArrayList<>(); } void add(Product product) { productList.add(product); } void showProductList() { for(Product product: productList) { System.out.println(product.info()); } } boolean isExistsProduct(Product product) { for(Product stock: productList) { if(stock.name().equals(product.name())){ return true; } } return false; } Product getProduct(String productName) { for(Product stock: productList) { if(stock.name().equals(productName)){ return stock; } } return new Product(); } }Product.javapackage vm; public class Product { private String productName; private int price; Product(String productName, int price) { this.productName = productName; this.price = price; } Product() { this.productName = ""; this.price = 0; } String name() { return productName; } int price() { return price; } String info() { String formatStr = "%s : %d円"; return String.format(formatStr, productName, price); } }
- 投稿日:2020-01-03T12:19:06+09:00
Javaで作る自動販売機(ドメイン駆動)
経緯
shiracamus様からのアドバイスを元に、DDD(ドメイン駆動)的に書き直しました。
その際、ロジックの見直しも一部行い、よりシンプルになるように致しました。処理フロー
・インスタンス生成
・商品リスト初期化(在庫補充)
・商品一覧表示(陳列商品の表示)
・商品選択(ユーザによる入力)
・入金(ユーザによる入力)
・販売(課金処理)
・おつり(返金処理)動作イメージ
ソースコード
Main.javapackage vm; import java.util.Scanner; public class Main { public static void main(String[] args) { // インスタンス生成 Store venderMachine = new Store(); @SuppressWarnings("resource") Scanner scanner = new Scanner(System.in); // 商品リスト初期化 Product water = new Product("水", 80); Product coke = new Product("コーラ", 100); Product orangejuice = new Product("オレンジジュース", 120); venderMachine.addStock(water); venderMachine.addStock(coke); venderMachine.addStock(orangejuice); // 商品一覧表示 System.out.println("≪販売中の商品です≫"); venderMachine.showProductList(); // 商品選択 Product purchase = null; String purchaseSelectMessage = "購入する商品名を入力してください。"; int purchaseSelectCount = 0; do { if(purchaseSelectCount > 0) { purchaseSelectMessage = "その商品の取り扱いはございません。販売中の商品からお選び下さい。"; } System.out.println(); System.out.println(purchaseSelectMessage); purchase = venderMachine.inStocker(scanner.next()); purchaseSelectCount++; } while(!venderMachine.isExistsStock(purchase)); // 入金 String depositMessage = "入金して下さい。"; int depositCount = 0; do { if(depositCount > 0) { depositMessage = "入金額が不足しています。不足分を追加してください。"; } System.out.println(); System.out.println(depositMessage); venderMachine.addDeposit(scanner.nextInt()); depositCount++; } while(!venderMachine.isEnoughDeposit(purchase)); // 販売 System.out.println(); venderMachine.sale(purchase); // おつり System.out.println(); venderMachine.change(); } }Store.javapackage vm; public class Store { private Stocker stocker; private Casher casher; Store() { stocker = new Stocker(); casher = new Casher(); } void addDeposit(int deposit) { casher.addDeposit(deposit); } void addStock(Product product) { stocker.add(product); } void change() { System.out.println("おつりは、" + casher.change() + "円です。"); } void showProductList() { stocker.showProductList(); } boolean isExistsStock(Product product) { return stocker.isExistsProduct(product); } boolean isEnoughDeposit(Product purchase) { return casher.isEnoughDeposit(purchase); } Product inStocker(String productName) { return stocker.getProduct(productName); } void sale(Product purchase) { casher.charge(purchase); System.out.println(purchase.name() + "です!"); System.out.println("ご購入ありがとうございました。"); } }Casher.javapackage vm; public class Casher { private int deposit; private int change; Casher(){ deposit = 0; change = 0; } void addDeposit(int deposit) { this.deposit += deposit; } void charge(Product purchase) { change = deposit - purchase.price(); } int change() { return change; } public boolean isEnoughDeposit(Product product) { if(deposit >= product.price()) { return true; } return false; } }Stocker.javapackage vm; import java.util.ArrayList; import java.util.List; public class Stocker { private List<Product> productList; Stocker() { productList = new ArrayList<>(); } void add(Product product) { productList.add(product); } void showProductList() { for(Product product: productList) { System.out.println(product.info()); } } boolean isExistsProduct(Product product) { for(Product stock: productList) { if(stock.name().equals(product.name())){ return true; } } return false; } Product getProduct(String productName) { for(Product stock: productList) { if(stock.name().equals(productName)){ return stock; } } return new Product(); } }Product.javapackage vm; public class Product { private String productName; private int price; Product(String productName, int price) { this.productName = productName; this.price = price; } Product() { this.productName = ""; this.price = 0; } String name() { return productName; } int price() { return price; } String info() { String formatStr = "%s : %d円"; return String.format(formatStr, productName, price); } }
- 投稿日:2020-01-03T10:35:47+09:00
アルゴリズム体操10
Sum of Two Values
整数の配列とある値を指定して、配列の二つの要素の合計が指定された値に等しくなるかどうかを判別します。
説明
CASE1: Target = 10の場合は2 + 8 = 10 となるので true を返します。
CASE2: Target = 20の場合は二つのペアが見つからないので false を返します。Solution
Runtime Complexity O(n)
配列全体を1回スキャンして、訪問した要素をハッシュセットに保存します。
スキャン中、配列内の要素 'e'について、ハッシュセットに 'val - e'が存在するかどうか、
つまり 'val - e'が既にアクセスされているかどうかを確認します。
ハッシュセットで val - eが見つかった場合、配列内にペア(e、val-e)があり、
その合計が指定されたvalと等しいことを意味します。よって true を返します。
配列内のすべての要素を使い果たし、そのようなペアが見つからなかった場合はfalseを返します。Memory Complexity O(n)
配列の要素分が入るHashSet を使うので、メモリはO(n)となります。
例
最初の例、つまり値= 10でこのアルゴリズムを実行してみましょう。
ここで、Targetの10とHashSetの中にある二つの要素2, 8の合計が10となるのでループは終了してtrueを返します。実装
findSum.javaimport java.util.HashSet; import java.util.Set; public class findSum { public boolean find_sum_of_two(int[] A, int val) { Set<Integer> found_values = new HashSet<>(); for (int a: A) { if (found_values.contains(val - a)) { return true; } found_values.add(a); } return false; } }Main.javapublic class Main { static findSum algo = new findSum(); static void test(int[] v, int val) { boolean output = algo.find_sum_of_two(v, val); System.out.println("exist(A, " + val + ") = " + (output ? "true" : "false") + "\n"); } public static void main(String[] args) { int[] v = new int[]{2, 1, 8, 4, 7, 3}; test(v, 3); test(v, 20); test(v, 1); test(v, 2); test(v, 7); } }Output
- 投稿日:2020-01-03T08:59:20+09:00
SpringToolSuite4にlombokをインストールする
STS4にlombokのインストールする方法
久しぶりにSTSでjava開発しようとしたら、lombokのインストール方法を忘れたのでメモ。
環境
Ubuntu:16.04.6 64bit
SpringToolSuite4:4.5.0.RELEASE
lombok:1.18.10Lombokのインストール
STSを閉じた状態でインストールを開始する。
lombokダウンロード
ダウンロードしたlombok.jarを実行ファイルのSpringToolSuite4と一緒の場所に配置。
lombok.jarをダブルクリックで起動し、Specifylocationをクリックして、SpringToolSuite4を指定する。
install/Updateをクリックして完了。
- 投稿日:2020-01-03T01:44:40+09:00
JavaのHashMapのソースを読む
いい加減ハッシュテーブルは実装レベルで理解しくべきということで,ざっと読んだときの気付きをメモしておく。10年前は読めなかったけど,今回はさくっと読めた。(読んだのは,Adopt Open JDK 13)
テーブル内部では以下のクラスでデータが管理されていた。要するにClosed Addressingで実装されている。
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; /** 中着 */ }余談だが,Eclipse Collection(10.1)のUnifiedMapもOpen Addressingだったが,こちらはポインタでチェインせずに配列でチェインしていた。Open Addressingのようにメモリのキャッシュを効かせたいことが理由の様子。
ハッシュ値の計算。
要素数が少ないときには,下位ビットだけで格納場所が決まってしまうことを避けるため,上位ビットも使えるように下位16bitに上位16bitのXORをとっていた。static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }ハッシュテーブルの引き方。
テーブルの要素数nから1引いたものとAND演算した値をindexとしていた。if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null);ちなみにテーブルの要素数(上記のtab.length)数は,2の倍数になっているようで,そこから-1すると全てのビットが立つので,下位ビットを残す演算になっている様子。














