【Spring/Jackson】Rest API : JSON RequestBodyのプロパティをEnum型としてマッピングする
メモ。
SpringのControllerでは、 @RequestBody
を使用することでHTTPリクエストボディに設定されたパラメータをJSONで受け取ることができます。
@RestController public class FluitController { // Request Body にJSONを指定するAPI @RequestMapping(value = "/fluits", method = RequestMethod.POST) public FluitResponse post(@RequestBody FluitRequest req) { .... } }
このとき、 HogeRequest
が以下のような構造で定義されているとします。
@Data public class FluitRequest { private String name; private Integer price; private Integer category; }
FluitRequest::category
は以下の値域のみとり得るとします
HogeRequest req = new HogeRequest(); req.setCategory(1); // 1 : りんご req.setCategory(2); // 2 : みかん req.setCategory(3); // 3 : ぶどう
FluitRequest::category
のような有限集合の場合、Javaであれば列挙型を使用して定義したいところです。
public enum FluitCategory { APPLE, ORANGE, GRAPE }
FluitRequest
にAPI /fluits
のPOSTリクエストボディがマッピングされるとき、category
プロパティの値がそのまま FluitCategory
列挙型としてマッピングされたら便利ですね。
Jacksonが提供する JsonDeserializer
を拡張定義することで、独自のマッピングを実装することができます。これを利用し、リクエストボディマッピング用クラス FluitRequest
に型としてFluitCategory
を持つプロパティを定義できるようにします。
まず、列挙型 FluitCategory
を以下のように整数型IDとの紐付けます。
// 各列挙型オブジェクトがidを持つ @Getter public enum FluitCategory { APPLE(1), ORANGE(2), GRAPE(3); private int id; private FluitCategory(int id) { this.id = id; } public static FluitCategory value(int id) { // idとマッチするFluitCategoryオブジェクトがない場合独自例外を送出 return Arrays.stream(values()).filter(x -> x.id == id).findFirst().orElseThrow(() -> new HogeException()); } }
次に、以下のような JsonDeserializer
拡張クラスを定義します。
拡張する JsonDeserializer
のジェネリクス型には変換先の型である FluitCategory
を指定しています。
変換元の値は deserialize
メソッドの引数 jsonParser
から取得できます。
今回は元々整数型の値であった category
を変換したかったので、 getIntValue
で値を取得します。この値を元に FluitCategory::value()
でFluitCategory
に変換し返り値とします。
public class FluitCategoryDeserializer extends JsonDeserializer<FluitCategory> { @Override public FluitCategory deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException { return FluitCategory.value(jsonParser.getIntValue()); } }
最後に、@Bean
として以下のような ObjectMapper
を定義します。
ここで定義されたObjectMapperがRequest BodyのJSONパース時に使用され、クライアントから整数型で送られたプロパティを FluitCategory
型プロパティとしてJava側で受け取った際に変換が実行されるようになります。
@Configuration public class JsonConfiguration { @Bean public ObjectMapper jsonObjectMapper() { ObjectMapper mapper = new ObjectMapper(); SimpleModule simpleModule = new SimpleModule(); simpleModule.addDeserializer(FluitCategory.class, new FluitCategoryDeserializer()); mapper.registerModule(simpleModule()); return mapper; }
【Spring】オリジナルの `.properties` ファイルを作成・読み込み
メモ。
Spring では application.properties
以外に独自の .properties
を生成し、その中で定義した値を使用することができます。
オリジナル .properties
ファイルの作成とプロパティの参照
例えば、オリジナルのプロパティファイル myconfig.properties
を以下のように作成したとします。
myconfig.properties
config.hoge=1 config.fuga='https://www.fuga.com' config.piyo.foo='foo.' config.piyo.bar=2
このプロパティファイルの設定をSpring内のコードにマッピングするための、 以下のようなクラスを定義します。
@Configuration @PropertySource("classpath:myconfig.properties") @ConfigurationProperties(prefix = "config") @Getter public final class MyConfig { public static class Piyo { private String foo; private Integer bar; } private Integer hoge; private String fuga; private Piyo piyo }
@PropertySource
指定したクラスにどのプロパティファイルの内容をマッピングするのかを指定します。 上の例では MyConfig
クラスに myconfig.properties
ファイルをマッピングするよう指定しています。 classpath:
はクラスパス配下であることを表し、 myconfig.properties
がクラスパスを通したディレクトリ下にあれば良いことを示しています。
@ConfigurationProperties
myconfig.properties
でどのようなプレフィックスを受け入れるかを設定しています。ここでは config
を指定しているので、MyConfigクラスはmyconfig.properties
の各パラメータ名の前に config.
を付けることを要求します。
マッピングの定義が完了したMyConfigクラスには @Configuration
(@Conponentが付加されたアノテーション)が付加されているので、下記のように他コンポーネントへDIによるインスタンス化を行うことができます。
@Service public class HogeService { private static final Logger logger = LoggerFactory.getLogger(LogUtils.class); @Autowired private MyConfig config; public void printFuga() { logger.info(config.getFuga()); } }
環境変数を参照する
下記のように @PropertySource
に渡すvalue値には外部環境変数への参照を記述することができます。
@PropertySource("classpath:myconfig-${ENV}.properties") // 環境変数を追
これにより、例えばデプロイ環境によって適用したいカスタム properties
ファイルの切り替えを行うことが可能です。
参考文献
【Angular】Angular CLIでライブラリの追加・ビルドを行う
Angular CLI
詳細は下記記事にて。
ng add
: 外部ライブラリを追加する
ng add
を使用すると、Angularプロジェクトへの外部ライブラリの追加を行えます(複数指定可能)。
Installing packages for tooling via npm.
の文言から、結局 npm
経由でパッケージインストールしていることがわかります。
$ ng add @angular/material @angular/cdk @angular/animations Installing packages for tooling via npm.
package.json
にも依存関係が記述されていることがわかります。
$ cat package.json | grep -E "material|cdk|animations" "@angular/animations": "~7.1.0", "@angular/cdk": "~7.2.0", "@angular/material": "^7.2.0",
ng build
: アプリをビルドする
ng build
でAngularプロジェクトをビルドし、 /dist
ディレクトリ配下にビルド済みファイルを出力します。
$ ng build Date: 2018-12-24T16:20:03.523Z Hash: bb05af5cc5e75024ef83 Time: 13417ms chunk {main} main.js, main.js.map (main) 10.9 kB [initial] [rendered] chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 223 kB [initial] [rendered] chunk {runtime} runtime.js, runtime.js.map (runtime) 6.08 kB [entry] [rendered] chunk {styles} styles.js, styles.js.map (styles) 179 kB [initial] [rendered] chunk {vendor} vendor.js, vendor.js.map (vendor) 3.69 MB [initial] [rendered] $ ls dist my-angular-app
ng update
: Angularプロジェクト及び依存ライブラリのバージョンをアップデートする
ng update
コマンドを実行すると、Angularアプリ及びアプリが依存するライブラリのバージョンをアップデートします。
$ ng update We analyzed your package.json and everything seems to be in order. Good work!
参考文献
【webpack】webpackを使用してWebアプリケーションをバンドルする
webpackとは
webPackとはモジュールバンドラと呼ばれるツールです。アプリーケーションが依存するjsモジュールを一つのjsファイルにまとめ上げ、依存関係の解決やトランスパイルを自動実行してくれます。内部的にモジュール同士の依存関係を表すグラフを自動で構築し、それに基づいて依存関係の解決をしてくれるので、設定ファイルなどなしに使用することができます(詳細な設定を記述したファイルを使用することもできます)。
・・・とはいったものの、「モジュールのバンドル」がいまいちピンと来なかったので、こちらのチュートリアルを実践してみました。
以下、チュートリアルのメモになります。
webpackのインストール
まず、webpack対象となる node
プロジェクトを作成します。
$ mkdir hello-webpack $ cd hello-webpack $ npm init -y
npm
でwebpackと、webpackのCLIツールをインストールします。
$ npm install webpack webpack-cli --save-dev
webpackを試しに使用する
実験環境準備
webpackを使用する前に、テスト用のモジュールを作ってみます。
最初のディレクトリ構成は以下の通り。
$ ls node_modules package.json package-lock.json
プロジェクトルートに以下のような index.html
を作成します。HTMLを見ると、 index.html
は <script>
タグ中でCDNによって茶刈 lodash.js
スクリプトをロードしていることがわかります。
<!doctype html> <html> <head> <title>Hello, webpack !!1!</title> <script src="https://unpkg.com/lodash@4.16.6"></script> </head> <body> <script src="./src/index.js"></script> </body> </html>
次に内部ロジック記述用スクリプトである src/index.js
を用意します。このスクリプトでは、先程 index.html
でロードしていた lodash
モジュールを使用しようとしているとします。が、 lodash
に対する依存関係が暗黙的で、 _
を未定義のグローバル変数として参照してしまっています。
function component() { let element = document.createElement('div'); element.innerHTML = _.join(['Hello', 'webpack'], ' '); return element; } document.body.appendChild(component());
上記の index.js
のような、参照できているようで参照できていない暗黙の依存関係は様々なバグを生んでしまいます。
このようなバグをなくすため、lodash
をnpmでのパッケージインストール & import に変更してしまいましょう!
npm での lodash
のインストール
先程用意した index.html
を、新たに生成した /dist
ディレクトリ配下に配置します。
dist └── index.html
次に、 lodash
をnpm経由でインストール。
$ npm install --save lodash
src/index.js
を修正します。明示的に lodash
を参照できるよう、下記一文をファイル先頭に追加します。
import _ from 'lodash';
dist/index.html
を編集します。
<script>
タグによる lodash
のロードをやめ、 参照先スクリプトを main.js
としました。これは後ほど使用するwebpackのバンドル後ファイルへの参照です。
webpackによるモジュールバンドル
上記の import
文によって、webpackが依存関係のグラフを自動作成可能になりました。
それでは、実際にwebpackでモジュールバンドルを行います。
npx
コマンドを使用し、 webpack
を実行します。
npx
についてはこちらを参照。
$ npx webpack Hash: 53680323beef520a74cd Version: webpack 4.28.2 Time: 445ms Built at: 2018/12/25 23:10:03 Asset Size Chunks Chunk Names main.js 1.03 KiB 0 [emitted] main Entrypoint main = main.js [0] ./src/index.js 189 bytes {0} [built] WARNING in configuration The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment. You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
webpackが成功し、 dist/main.js
が生成されました!
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t){document.body.appendChild(function(){let e=document.createElement("div");return e.innerHTML=_.join(["Hello","webpack"]," "),e}())}])
どうやら「モジュールのバンドル」 とは
- 依存モジュールを一つの
js
にまとめること - スクリプトのミニマイズもする
- トランスパイルもする
といった操作のようです。
webpackを使ってみての感想ですが、「アプリの最適化」を行うのには素晴らしいツールだなと思いました。 アプリの複雑な依存関係を解決し一つのファイルにアプリのスクリプトをまとめることで、ビルド・デプロイするアプリを軽量化・高速化できるので、アプリの総仕上げとして良いのではないでしょうか。 (多くのフロントエンドCLIでもデフォルトで採用されているみたいですね)
参考文献
【Angular】Angular CLIで簡単にAngularプロジェクトの作成・デプロイ・テストを自動化する
Angular CLI
Angular CLIはGoogleが提供するフロントエンドフレームワークAngularのためのCLIツールです。Angularプロジェクトの作成やデプロイ、テスト自動化などをより簡単に実行することができます。
インストール
npm
でインストールします。
$ npm install -g @angular/cli
2018/12/24現在のバージョンだと7系のCLIがインストールされると思います。
$ ng --version _ _ ____ _ ___ / \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _| / △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | | / ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | | /_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___| |___/ Angular CLI: 7.1.4 Node: 10.3.0 OS: darwin x64 Angular: ... Package Version ------------------------------------------------------ @angular-devkit/architect 0.11.4 @angular-devkit/core 7.1.4 @angular-devkit/schematics 7.1.4 @schematics/angular 7.1.4 @schematics/update 0.11.4 rxjs 6.3.3 typescript 3.1.6
ng new
: プロジェクトを作成する
ng new
でAngularプロジェクトディレクトリを作成することができます。
$ ng new
コマンドを実行すると、対話形式で下記項目について聞いてくるので、それぞれ入力or選択。
- プロジェクト名
- Angularルーティングの有効化
- スタイルシートのフォーマットの選択
? What name would you like to use for the new workspace and initial project? my-angular-app ? Would you like to add Angular routing? Yes ? Which stylesheet format would you like to use? SCSS [ http://sass-lang.com ]
これでプロジェクトは完成です。
$ cd my-angular-app $ ls README.md e2e package-lock.json src tslint.json angular.json node_modules package.json tsconfig.json
生成したプロジェクトには、既にテスティングフレームワークやLinterなどのモジュールがインストールされています。Angularが2系より採用したTypeScriptも確認できます。
$ cat package.json { "name": "my-angular-app", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e" }, "private": true, "dependencies": { "@angular/animations": "~7.1.0", "@angular/common": "~7.1.0", "@angular/compiler": "~7.1.0", "@angular/core": "~7.1.0", "@angular/forms": "~7.1.0", "@angular/platform-browser": "~7.1.0", "@angular/platform-browser-dynamic": "~7.1.0", "@angular/router": "~7.1.0", "core-js": "^2.5.4", "rxjs": "~6.3.3", "tslib": "^1.9.0", "zone.js": "~0.8.26" }, "devDependencies": { "@angular-devkit/build-angular": "~0.11.0", "@angular/cli": "~7.1.4", "@angular/compiler-cli": "~7.1.0", "@angular/language-service": "~7.1.0", "@types/node": "~8.9.4", "@types/jasmine": "~2.8.8", "@types/jasminewd2": "~2.0.3", "codelyzer": "~4.5.0", "jasmine-core": "~2.99.1", "jasmine-spec-reporter": "~4.2.1", "karma": "~3.1.1", "karma-chrome-launcher": "~2.2.0", "karma-coverage-istanbul-reporter": "~2.0.1", "karma-jasmine": "~1.1.2", "karma-jasmine-html-reporter": "^0.2.2", "protractor": "~5.4.0", "ts-node": "~7.0.0", "tslint": "~5.11.0", "typescript": "~3.1.6" } }
ng serve
: Angularプロジェクトからアプリをローカルにデプロイする
ng serve
で、ローカルサーバを立ち上げアプリをデプロイできます。
$ cd my-angular-app $ ng serve ** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ ** Date: 2018-12-24T13:31:23.110Z Hash: 5c43a53e1dad2b4bcc2c Time: 11797ms chunk {main} main.js, main.js.map (main) 11.5 kB [initial] [rendered] chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 223 kB [initial] [rendered] chunk {runtime} runtime.js, runtime.js.map (runtime) 6.08 kB [entry] [rendered] chunk {styles} styles.js, styles.js.map (styles) 16.6 kB [initial] [rendered] chunk {vendor} vendor.js, vendor.js.map (vendor) 3.67 MB [initial] [rendered] ℹ 「wdm」: Compiled successfully.
http://localhost:4200/
にアクセスすると、以下のようなデフォルトアプリが表示されます。
ng generate
: プロジェクトに必要なモジュールを追加する
ng generate
を使用すると、classやdirective、serviceといったモジュールを特定のディレクトリ配下(デフォルトで src/apps
)に生成してくれます。
$ ng generate class HttpHelper CREATE src/app/http-helper.ts (28 bytes)
$ ls src/app/http-helper.ts src/app/http-helper.ts
--force
-f
をつけると、既存のファイルを上書きして生成します。
$ ng generate class HttpHelper --force
ng lint
: linterでコードを検査する
ng lint
を実行すると既存のソースコードに対しLinterが起動し、lintの設定に則ったルールのもと文法ミスやアンチパターンを指摘してくれます。
$ ng lint Linting "my-angular-app"... All files pass linting. Linting "my-angular-app-e2e"... All files pass linting.
ng test
: 単体テスト実行
ng test
で、プロジェクト内で単体テストを実行します。
デフォルトでは、テストランナーとしてKarmaによるテストが実行されます。
なおテストコード自体にはデフォルトでテスティングフレームワークJasmineが使われているようです。
$ ng test 11% building modules 9/9 modules 0 active24 12 2018 22:47:10.440:WARN [karma]: No captured browser, open http://localhost:9876/ 24 12 2018 22:47:10.447:INFO [karma-server]: Karma v3.1.4 server started at http://0.0.0.0:9876/ 24 12 2018 22:47:10.447:INFO [launcher]: Launching browsers Chrome with concurrency unlimited 24 12 2018 22:47:10.464:INFO [launcher]: Starting browser Chrome 24 12 2018 22:47:18.729:WARN [karma]: No captured browser, open http://localhost:9876/ 24 12 2018 22:47:19.418:INFO [Chrome 71.0.3578 (Mac OS X 10.14.1)]: Connected on socket U_QKpHniSB3g0GIZAAAA with id 75357283 Chrome 71.0.3578 (Mac OS X 10.14.1): Executed 3 of 3 SUCCESS (0.288 secs / 0.245 secs) TOTAL: 3 SUCCESS TOTAL: 3 SUCCESS
Karmaについてはこちらの記事参照。
ng e2e
E2Eテスト実行
ng e2e
で、E2Eテストが開始されます。
$ ng e2e ** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ ** Date: 2018-12-24T13:51:46.733Z Hash: 5c43a53e1dad2b4bcc2c Time: 11117ms chunk {main} main.js, main.js.map (main) 11.5 kB [initial] [rendered] chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 223 kB [initial] [rendered] chunk {runtime} runtime.js, runtime.js.map (runtime) 6.08 kB [entry] [rendered] chunk {styles} styles.js, styles.js.map (styles) 16.6 kB [initial] [rendered] chunk {vendor} vendor.js, vendor.js.map (vendor) 3.67 MB [initial] [rendered] [22:51:47] I/file_manager - creating folder /Users/aa367417/hoge/my-angular-app/node_modules/protractor/node_modules/webdriver-manager/selenium [22:51:47] I/config_source - curl -o/Users/aa367417/hoge/my-angular-app/node_modules/protractor/node_modules/webdriver-manager/selenium/chrome-response.xml https://chromedriver.storage.googleapis.com/ ℹ 「wdm」: Compiled successfully. [22:51:48] I/downloader - curl -o/Users/aa367417/hoge/my-angular-app/node_modules/protractor/node_modules/webdriver-manager/selenium/chromedriver_2.45.zip https://chromedriver.storage.googleapis.com/2.45/chromedriver_mac64.zip [22:51:49] I/update - chromedriver: unzipping chromedriver_2.45.zip [22:51:49] I/update - chromedriver: setting permissions to 0755 for /Users/aa367417/hoge/my-angular-app/node_modules/protractor/node_modules/webdriver-manager/selenium/chromedriver_2.45 [22:51:50] I/launcher - Running 1 instances of WebDriver [22:51:50] I/direct - Using ChromeDriver directly... Jasmine started workspace-project App ✓ should display welcome message Executed 1 of 1 spec SUCCESS in 2 secs. [22:51:58] I/launcher - 0 instance(s) of WebDriver still running [22:51:58] I/launcher - chrome #01 passed
簡単ですが、Angular CLIをいじってみました。 既に使用しているVue CLIと比較すると、フレームワークがフルスタックなのもあり、プロジェクト生成時に入れるパッケージに悩まなくていいですね。
参考文献
【Node.js】npxでローカルにパッケージを一時的にインストールして実行する
npx
npx
はローカルにインストールしたnpmパッケージバイナリを直接実行できるコマンドです。npm5.2.0より導入されました。
このnpx
にはパッケージを一時的にだけインストールしてその機能を実行する機能が備わっているので、
今回はその機能について紹介します。
npx
のインストール
まずは npm
自体のアップデートを行います。
$ npm install -g npm
5.2.0以上であることを確認。
$ npm --version 6.2.0
確認できたら、 npx
をインストールします。
$ npm install -g npx
インストールしていないパッケージを実行
ローカルにインストールしていないパッケージを実行することができる npx
ですが、一旦はパッケージのインストールが走ります。
が、コマンド実行後インストールされたままにならず削除されるので、ローカルの node_modules
や package.json
を汚しません。
以下はAngular CLI のバージョンチェックを実行している例です。
$ npx @angular/cli --version _ _ ____ _ ___ / \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _| / △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | | / ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | | /_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___| |___/ Angular CLI: 7.1.4 Node: 8.11.3 OS: darwin x64 Angular: ... Package Version ------------------------------------------------------ @angular-devkit/architect 0.11.4 @angular-devkit/core 7.1.4 @angular-devkit/schematics 7.1.4 @schematics/angular 7.1.4 @schematics/update 0.11.4 rxjs 6.3.3 typescript 3.1.6
実行したパッケージのコマンドを実行してみるとできないことがわかります。 パッケージがインストールされず、そのまま破棄されていることがわかります。
# インストールはされていない $ ng zsh: correct 'ng' to 'bg' [nyae]? n
npmパッケージを手軽に、試しに実行したい場合などに便利です。
参考文献
【IBM COS】IBM Cloud Object Storageで署名付きURLを発行する
IBM Cloud Object Storageとは
IBM Cloud Object Storage(COS)はIBMが クラウド上で提供するストレージサービスです。IBM Cloud上でAWSのS3のようなオブジェクトベースでリソースを保存できるストレージ環境を提供します。IBM Cloud上でサービスとして提供されているので、クラウド上に立ち上げたアプリケーションサーバと連携して画像や音声の保存用ストレージとして利用する・・・といったことが可能です。それだけでなく、オンプレミス環境でホスト型クラウド環境を構築することでもCOSを利用することができます。
COSが使われるシーン
多くの場合、Webアプリケーションやシステムで使われるDB(RDBMS)では、文字列や数値といったデータと、それらのデータからなるいわゆる構造化データが扱われます。例えばRDBの場合、テーブルに挿入されるレコードが構造化データになります。このような構造化データはスキーマによって明確に定義されるため、RDBで管理しやすい傾向にあります。
しかし、画像や音声といったいわゆる非構造化データは、構造化されたデータを扱うシステムとはあまり相性がよくありません。例えば画像バイナリをRDBに保存する場合、数MBに及ぶバイナリを文字列(Base64など)に変換してどこかのテーブルの1カラムの値として保存する、といったことなどをやらなければいけません。このような実装は、RDBの設計にいびつな構造をもたらします。
そこで、そのような非構造化データは一旦別の場所に追いやり、そのデータへのアクセスポイントをRDBレコードのカラム値として保存するやり方が考えられます。そのような場合にCOSが威力を発揮します。
COSはKVSライクにデータを保存するオブジェクトベースのストレージです。特定のオブジェクトデータに一意なキーを対応付け、それをもとにアクセスポイントを発行してデータの更新/参照を可能にします。例えば画像であれば、COSに保存した画像のオブジェクトキーからURLを発行することができます。
IBM COSの署名付きURL
COSに保存したリソースを公開するとき、特定サービスのユーザに対してのみ限定公開したい場合があると思います。COSのエンドポイントをprivateにする方法もありますが、スマートフォンなどパブリックなネットワークに接続するクライアントに対してデータを提供したい場合など、適用しにくい場合も考えられます。
そのような場合、署名付きURLが使えます。COSの設定でエンドポイントに対するアクセスを原則禁止としつつ、署名付きのURL経由でのみリソースにアクセスさせるといったことが可能なため、パブリックなネットワークに接続するクライアントに対してもリソースの提供に対応できます。
署名付きURLの発行
AWS CLI
署名付きURLは、AWS CLIを利用して発行することができます。 Macの場合、 Homebrewでインストールすることができます。
$ brew install awscli
Homebrew、およびAWS CLIのインストールには下記も参考にしてください。
インストール後、aws configure
でCOSエンドポイントの設定を行います。
$ aws configure AWS Access Key ID [None]: {Access Key ID} AWS Secret Access Key [None]: {Secret Access Key} Default region name [None]: {Provisioning Code} Default output format [None]: json
設定後、下記コマンドを実行することで署名付きURLを発行できます。
--endpoint-url
には署名付きURLを跛行したいしたいリソースのCOSエンドポイントを、
{bucketName}
にはリソースのバケット名、 {objKey}
にはオブジェクトキーを入力してください。
$ aws --endpoint-url=https://{endpoint} s3 presign s3://{bucketName}/{objKey}
バケットはCOS内で一意な保存領域を表し、複数のバケットを一つのCOSサービス内に生成することでリソースの保存領域を論理的に分割することができます。オブジェクトキーはCOS内リソースを一意に識別するためのキーになります。
また、下記のように署名付きURLには有効期限を任意に設定することができます。
$ aws --endpoint-url=https://{endpoint} s3 presign s3://bucket-1/new-file --expires-in 604800000
Java
COSのSDKを利用して、プログラム中でも署名付きURLを発行することができます。 今回はJavaを題材としてコード例を示します。
build.gradle
dependencies { ... implementation('com.ibm.cos:ibm-cos-java-sdk:latest.release') ... }
Java
private String getPreSignedUrl(String bucketName, String objectKey) { // COS認証情報(実際の運用では環境変数等で外出ししてください) String accessKey = "XXXXXX"; String secretKey = "XXXXXXXXX"; String endpoint = "XXXXXXXXXX"; String region = "XXXXXXXX"; // 署名有効期限までの時間 long signExpiration = 604800000; // 1week // 認証情報 AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); // クライアント設定 ClientConfiguration clientConfig = new ClientConfiguration(); clientConfig.setProtocol(Protocol.HTTPS); // プロトコル clientConfig.setConnectionTimeout(10000); // 接続タイムアウト(ms) // COSエンドポイント設定 EndpointConfiguration endpointConfiguration = new EndpointConfiguration(endpoint, region); // クライアント生成 AmazonS3 client = AmazonS3ClientBuilder.standard() .withCredentials(new AWSStaticCredentialsProvider(credentials)).withClientConfiguration(clientConfig) .withEndpointConfiguration(endpointConfiguration).build(); // 署名有効期限 Date expiration = new Date(); long msec = expiration.getTime() + signExpiration; expiration.setTime(msec); // 署名つきURL生成 GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, objectKey); generatePresignedUrlRequest.setMethod(HttpMethod.GET); generatePresignedUrlRequest.setExpiration(expiration); String url = client.generatePresignedUrl(generatePresignedUrlRequest).toString(); return url; }
上記メソッドでは既にCOS上に保存されているリソースが置かれているバケット名と、リソースオブジェクトキーを受け取り、署名付きURLを生成しています。
署名付きURLによるリソースへのアクセス
生成した署名付きURLは以下のようになります(CredentialやSignatureなど、一部ぼかした表現にしています)。
https://hogehoge.us-south.objectstorage.softlayer.net/fugafuga.JPEG?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20181222T101615Z&X-Amz-SignedHeaders=host&X-Amz-Expires=604800&X-Amz-Credential=[Credential...]&X-Amz-Signature=[Signature...]
署名付きURLを生成すると、以下のようなパラメータがURLに付加されます。これらはURLに対する署名情報を表しています。
X-Amz-Algorithm | ダイジェスト計算のためのハッシュアルゴリズム |
X-Amz-Date | URL生成時刻 |
X-Amz-SignedHeaders | 署名対象ヘッダ情報 |
X-Amz-Expires | URL署名有効期限 |
X-Amz-Credential | 認証情報 |
X-Amz-Signature | 署名 |
これら署名情報のないURLにアクセスするとHTTPステータスコード403が返され、「Access Denied」となりリソースにアクセスすることができません。
また署名付きURLの有効期限は任意に設定することができます。今回は有効期限を一週間としています。 有効期限を過ぎた署名付きエンドポイントにアクセスすると、下記のようなレスポンスが返され、リソースにアクセスできなくなります。
このように、リソースへのアクセス手段を署名付きURL経由で提供することで、COSに保存されたリソースをより安全に共有できるようになります。