K4750.NET

RestControllerのスタックトレースをStringで得るコード

Spring BootのRestController実行時のスタックトレースをString化してみる。

package com.example.demo;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.stream.Stream;

@RestController
public class StackTrace {
    @RequestMapping("printStackTrace")
    public String printStackTrace() {
        return Stream.of(Thread.currentThread().getStackTrace())
                .skip(1)
                .map(StackTraceElement::toString)
                .reduce((a, ste) -> a + "\n" + ste)
                .get();
    }
}

skip(1)がないと、getStackTrace()の呼び出しまでスタックトレースに含まれてしまう。実行結果はこんな感じ。

com.example.demo.StackTrace.printStackTrace(StackTrace.java:12)
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.base/java.lang.reflect.Method.invoke(Method.java:566)
org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526)
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1579)
org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
java.base/java.lang.Thread.run(Thread.java:834)

日本初の自作キーボード専門店「遊舎工房」へ行ってきた

秋葉原(というか末広町)の自作キーボード専門店へ行ってきた。また、久々の秋葉原訪問なので、ついでに各店舗をめぐってキーボード数を再カウントしたかったが・・・ツクモeX.だけ寄って今日はおしまい。


1.遊舎工房

東京メトロ銀座線の末広町駅から御徒町方面へ徒歩5分程度。狭いお店に人が賑わっていた。

  • キーキャップのガチャガチャが・・・これは目当てではない^^;
  • ロープロファイル用のキーキャップが¥3,000ちょい(だったかな?)欲しいけど・・・その前にそのためのロープロファイルスイッチが欲しいところ。Kailhロープロファイルスイッチの赤軸が欲しいが、オンラインショップでは在庫無し。店舗はどうなのかな(結局聞かず)。
  • 自作キーボード関連の同人誌が5~6冊だか置いてあって、試し読みも可。数冊はすでに持っているものだったが・・・C95で販売された本(KbD C95 DECEMBER 2018)は初見だった。個人的にはPDFで欲しいので、BOOTHあたりで電子書籍版を待つかな^^;
  • 海外からバカ高い送料払ってキーキャップを取り寄せていたのはいつの時代だか・・・大量のキーキャップが店内にずらり。
  • 店内の工作スペースで早速作られている?方々が。
  • カウンターに展示されているのはHelixかな・・・ちょっと人が居すぎて近寄れず・・・あぁ帰宅時間が・・・T-T

2.ツクモeX.

あれ?展示されているキーボードのマニアックさが薄れて、なおかつキーボード数が減ったみたい。大量にあった左右分離キーボードも見かけなかったような?最近はキーボードにこだわるような人は自作キーボードに流れて行って、出来合いのキーボードは売れなくなってきているということなのかな・・・。

Angularでcowsayしてみた

オライリーの「Docker」本で初めてcowsayコマンドの存在を知った・・・Angularで出来るかな?


1.Angularプロジェクト作成

いつものやつ。

$ ng new cowsayangular7

2.cowsayインストール

ずばりcowsayパッケージがあるので、これをインストールする。

$ cd cowsayangular7
$ npm install --save cowsay

3.AppComponent変更

とりあえず、app.component.htmlは超シンプルにして、

<pre>{{title}}</pre>

app.component.tsにimportを追加して、title行を変更し、

import { Component } from '@angular/core';
import { say } from 'cowsay/build/cowsay.es.js';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = say({ text: 'Hello Angular7 World!'});
}

app.component.scssはこんな感じか。

pre {
  font-family: "Lucida Console";
  font-size: 32px;
}

4.Cow say “Hello Angular7 World!”

実行してみる。

$ ng serve --open

出た出た^^

Hello Angular7 World

DockerfileのCOPYの動きを確認してみる

DockerfileのCOPY命令がファイルの内容に変化がなければキャッシュを使うことは分るが・・・ファイルが消えた時等はどうなるのだろう・・・ということで試してみた。


1.サンプルファイル

Dockerfile
src\
   a.txt
   b.txt
   c.txt

Dockerfileの内容:

FROM alpine:latest
COPY src dst

2.初回ビルド

$ docker build -t copytest .
Sending build context to Docker daemon  4.096kB
Step 1/2 : FROM alpine:latest
latest: Pulling from library/alpine
4fe2ade4980c: Already exists
Digest: sha256:621c2f39f8133acb8e64023a94dbdf0d5ca81896102b9e57c0dc184cadaf5528
Status: Downloaded newer image for alpine:latest
 ---> 196d12cf6ab1
Step 2/2 : COPY src dst
 ---> 7a3d51886012
Successfully built 7a3d51886012
Successfully tagged copytest:latest

COPY結果を確認してみる。

$ docker run --rm copytest ls -l /dst
total 0
-rwxr-xr-x    1 root     root             0 Oct 27 14:25 a.txt
-rwxr-xr-x    1 root     root             0 Oct 27 14:25 b.txt
-rwxr-xr-x    1 root     root             0 Oct 27 14:25 c.txt

3.2回目ビルド

ホスト環境を何も変えず、2回目を実行してみる。

$ docker build -t copytest .
Sending build context to Docker daemon  4.096kB
Step 1/2 : FROM alpine:latest
 ---> 196d12cf6ab1
Step 2/2 : COPY src dst
 ---> Using cache
 ---> 7a3d51886012
Successfully built 7a3d51886012
Successfully tagged copytest:latest

COPYは実行されず、キャッシュが使われている。よって、COPY結果も変わらず。


4.ファイルを追加する

まずはファイルを追加してみる。

Dockerfile
src\
   a.txt
   b.txt
   c.txt
   d.txt <- ファイルを追加する

ビルドを実行してみると・・・

$ docker build -t copytest .
Sending build context to Docker daemon  4.608kB
Step 1/2 : FROM alpine:latest
 ---> 196d12cf6ab1
Step 2/2 : COPY src dst
 ---> 4584e2974830
Successfully built 4584e2974830
Successfully tagged copytest:latest

キャッシュは使われずに、COPYが実行されている。結果を確認してみる。

$ docker run --rm copytest ls -l /dst
total 0
-rwxr-xr-x    1 root     root             0 Oct 27 14:25 a.txt
-rwxr-xr-x    1 root     root             0 Oct 27 14:25 b.txt
-rwxr-xr-x    1 root     root             0 Oct 27 14:25 c.txt
-rwxr-xr-x    1 root     root             0 Oct 27 14:25 d.txt

5.追加したファイルを削除する

先ほど追加した d.txt を削除して、ビルドを実行してみると、

$ docker build -t copytest .
Sending build context to Docker daemon  4.096kB
Step 1/2 : FROM alpine:latest
 ---> 196d12cf6ab1
Step 2/2 : COPY src dst
 ---> Using cache
 ---> 7a3d51886012
Successfully built 7a3d51886012
Successfully tagged copytest:latest

予想に反して(?)、(d.txtを追加する前に作成した)キャッシュが使われている。賢い^^;


6.さらにファイルを削除する

さらに c.txt を削除して、ビルドを実行してみると、

$ docker build -t copytest .
Sending build context to Docker daemon  3.584kB
Step 1/2 : FROM alpine:latest
 ---> 196d12cf6ab1
Step 2/2 : COPY src dst
 ---> c8f23fe46a11
Successfully built c8f23fe46a11
Successfully tagged copytest:latest

ファイルが消えたことを検知して、COPYが実行された。dstの内容は・・・

$ docker run --rm copytest ls -l /dst
total 0
-rwxr-xr-x    1 root     root             0 Oct 27 14:25 a.txt
-rwxr-xr-x    1 root     root             0 Oct 27 14:25 b.txt

期待通りの動きだ^^

オライリーの「Docker」のサンプルが動かなかったので修正してみる

約2年前の2016年8月に発行されたオライリー「Docker」。5章「開発でのDockerの利用」を実際に手を動かして確認してみたところ、所々動かない個所があったので修正してみる。


1.「5.1 “Hello World!”」

コンテナ内で実行してみたがエラーになる・・・どうやら2行目がインデントされているのが問題のようだ。

from flask import Flask
    app = Flask(__name__) 

@app.route('/') 
def hello_world():
    return 'Hello World!\n'

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')

下記 Dockerfile は問題なし。

FROM python:3.4
RUN pip install Flask==0.10.1
WORKDIR /app
COPY app /app
CMD ["python", "identidock.py"]

コンテナのビルド&起動も問題なし。

$ docker build -t identidock .
$ docker run -d -p 5000:5000 identidock

curlコマンドによる動作確認は・・・Windows環境なのでcurlがないのと、コンテナ内へホスト環境からアクセスするには上記Pythonコードの「host=0.0.0.0」をコンテナに割り当てられたIPアドレスに書き換える必要(?)があるようなので・・・せっかくなのでcurlがインストールされたDockerイメージを使って動作確認してみる。

$ docker run --rm -it --entrypoint /bin/sh byrnedo/alpine-curl
/ # curl http://172.17.0.3:5000/
Hello World!

IPアドレス「172.17.0.3」は、identidockイメージから起動されたコンテナのアドレス。本来ならば –link 等を使ってもっとエレガントにやるのだろうが・・・。続いて、アプリケーションサーバ uWSGI を使うためのDockerfile。

FROM python:3.4
RUN pip install Flask==0.10.1 uWSGI==2.0.8
WORKDIR /app
COPY app /app
CMD ["uwsgi", "--http", "0.0.0.0:9090", "--wsgi-file", "/app/identidock.py", \
     "--callable", "app", "--stats", "0.0.0.0:9191"]

uWSGIのコンパイル(?)エラーが出力されて、イメージのビルドに失敗する。。Pythonのバージョンを変えたり、FlaskやuWSGIのバージョン指定を外したりしても改善しなかったので、結局UbuntuをベースにPython3.6をインストールするやり方にしてみた。

FROM ubuntu:18.04
RUN apt-get update -y && apt-get install -yq python3-pip python3-dev wget && \
    wget https://bootstrap.pypa.io/get-pip.py && python3.6 get-pip.py && \
    pip install Flask uWSGI
WORKDIR /app
COPY app /app
CMD ["uwsgi", "--http", "0.0.0.0:9090", "--wsgi-file", "/app/identidock.py", "--callable", "app"]

CMD行も”–stats”あたりでエラーが出てコンテナが起動できなかったので、パラメタを削除して起動するようにした。