【初心者】SpringBootでTodoアプリのREST APIを書いてみる
追記
- Controllerのコードの一部が切れていたので修正
- Controllerのアノテーション周りの話が抜けていたので追記
内容
- Java初心者が夏休み中にSpringの勉強をする
- Spring Frameworkはハードルが高そうだったので一先ずSpringBootから
- お題は初心者がよくやるTodoアプリのREST API
詳しく触れないこと
- gradle周辺
- Database周辺
- JPA
- H2 Database
本編
- 開発は
IntelliJ IDEA
を使用- ビルドとかアプリケーションの起動はお任せ
雛形作成
- https://start.spring.io/ で作成
- Project:
Gradle Project
- Language:
Java
- Spring Boot:
2.1.8
- Project Metadaba:
- Group:
com.example
- Artifact:
todoapp
- Options: そのまま
- Group:
- Dependencies:
Spring Web
Spring Data JPA
H2 Database
Lombok
- Project:
- ダウンロードされたzipファイルをunzipしてIntelliJでopenしたところから開始
パッケージ構成
com.example.todoapp
直下に以下を並べるcontrollers
repositories
models
services
とかもあるべきかもしれないけど今回は割愛
コード
models
- Task.java
package com.example.todoapp.models; import lombok.Data; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Data @Entity public class Task { @Id @GeneratedValue private Long id; private String summary; private Boolean done = false; public Task(){} }
- 各アノテーションについて
@Data
- 各フィールドのsetterとgetterを定義してくれる
toString()
メソッドやhashCode()
メソッドを定義してくれる
@Entity
- エンティティであることを示す。
- このクラスのフィールドを持つテーブルがDBに作られる
@Id
- エンティティの主キーのフィールドを指定する
@GeneratedValue
- 主キーの値の生成方法を指定する
strategy
やgenerator
を指定して挙動を変えられそう(試してない)
repositories
- TaskRepository.java
package com.example.todoapp.repositories; import com.example.todoapp.models.Task; import org.springframework.data.jpa.repository.JpaRepository; public interface TaskRepository extends JpaRepository<Task, Long> { }
JpaRepository
を継承したinterfaceを定義JpaRepository
の祖先にいるCrudRepository
インターフェースがfindById
などのインターフェースを定義している- 実装は
SimpleJpaRepository
なのかな?
JpaRepository
は対象のエンティティクラスとその主キーの型を指定する- ここで指定した主キーの型は、
findById
やdeleteById
の引数の型として使われる - 今回作成した
Task
エンティティの主キーはLong
型なのでここではLong
を指定
- ここで指定した主キーの型は、
controllers
- ApiController.java
package com.example.todoapp.controllers; import com.example.todoapp.models.Task; import com.example.todoapp.repositories.TaskRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Optional; @RestController @RequestMapping(value = "/api") public class ApiController { @Autowired TaskRepository taskrepo; @GetMapping(value = "/todos") public List<Task> tasklist(){ return taskrepo.findAll(); } @GetMapping(value = "todo/{id}") public Optional<Task> retrieve(@PathVariable Long id){ return taskrepo.findById(id); } @PostMapping(value = "/todo") public ResponseEntity<Task> newTask(@RequestBody Task task){ Task result = taskrepo.save(task); return new ResponseEntity<Task> (result, HttpStatus.CREATED); } @PutMapping(value = "/todo/{id}") public Task update(@PathVariable Long id, @RequestBody Task task){ task.setId(id); return taskrepo.save(task); } @DeleteMapping(value = "/todo/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) public void delete(@PathVariable Long id){ taskrepo.deleteById(id); } }
- 各アノテーションについて
@RestController
- 各メソッドの戻り値がそのままレスポンスボディになる
@Controller
を使う場合、メソッドの戻り値はStringにしてテンプレートに埋め込む形で使うらしい
@RequestMapping
- このコントローラが受け付けるリクエストのパスやメソッドやクエリパラメータを指定できる
@Autowired
@Component
アノテーションをつけて登録されているBeanとの紐付けを行う@Repository
や@Service
をつけたクラスも、内部では@Component
が付加されている- ここでは
TaskRepository
インターフェースを満たすクラスが自動的にtaskrepo
に格納される
@GetMapping
,@PostMapping
,@PutMapping
,@DeleteMapping
@RequestMapping
のメソッドを固定したもの@GetMapping
の場合、@RequestMapping(method=RequestMethod.GET)
と同等
@ResponseStatus
- デフォルトのステータスコードを指定する
200
以外を返したい時に使う?
動作確認
新規登録
$ curl http://localhost:8080/api/todo -XPOST -H 'Content-Type: application/json' -d '{"summary": "my first task"}' | jq . % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 75 0 47 100 28 170 101 --:--:-- --:--:-- --:--:-- 170 { "id": 1, "summary": "my first task", "done": false }
リスト取得
$ curl http://localhost:8080/api/todos | jq . % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 49 0 49 0 0 285 0 --:--:-- --:--:-- --:--:-- 286 [ { "id": 1, "summary": "my first task", "done": false } ]
1件取得
$ curl http://localhost:8080/api/todo/1 | jq . % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 47 0 47 0 0 2045 0 --:--:-- --:--:-- --:--:-- 2136 { "id": 1, "summary": "my first task", "done": false }
更新
$ curl http://localhost:8080/api/todo/1 -XPUT -H 'Content-Type: application/json' -d '{"summary": "updated my task", "done": true}' | jq . % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 92 0 48 100 44 2632 2412 --:--:-- --:--:-- --:--:-- 2666 { "id": 1, "summary": "updated my task", "done": true }
削除(レスポンスボディがないのでStatusCodeで確認)
$ curl http://localhost:8080/api/todo/1 -XDELETE -v * Trying ::1... * TCP_NODELAY set * Connected to localhost (::1) port 8080 (#0) > DELETE /api/todo/1 HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.54.0 > Accept: */* > < HTTP/1.1 204 < Date: Wed, 25 Sep 2019 15:29:37 GMT < * Connection #0 to host localhost left intact
まとめ
- 動かしたいもの自体はめっちゃ簡単に動かせた
- アノテーション種類多すぎて何使えばいいのかわからん
- とりあえず今回使ったものくらいは最低限おさえておきたい
- あとは何かするときに都度調べられればよさそう
OracleXE 11gの SJIS化
注釈
元記事は↓
筆者は同一
この記事について
- OracleDatabase Express Edition(XE) 11g のDockerコンテナで文字コードを変更するためにやったことを書く(N番煎じ)
- 11gでは文字コードはデフォルトでUTF8となっており、変更するためにはDBを再構築する必要がある
- 単体テストとかで使いたいので、起動したときに既に文字コードが変更されている状態にしたい
- 「え、今の時代に11g…?」
- 仰るとおりだと思いますが、古いシステムだし皆さんのところにもそういうのありますよね?ね?
前提
- oracleが公開しているDockerfileを使って作成したイメージをベースイメージとする
- 記事作成時点での最新コミットは
0972d17
- 記事作成時点での最新コミットは
手順
- 他の記事でたくさん紹介されているのでそちらを参照
追加で修正する箇所
CMD
コマンドで実行されている$ORACLE_BASE/$RUN_FILE
を実行しないようにする必要がある- 起動時に
/etc/init.d/oracle-xe configure
が実行されないようにするため
- 起動時に
- 以下のことを自前で行う
tnsnames.ora
の書き換えlistener.ora
の書き換え/etc/init.d/oracle-xe configure
に渡すrspファイルの書き換え- SYSとSYSTEM向けのパスワードの部分
上記もろもろに対応したDockerfile
確認
- コンテナを起動
$ docker run --name oracle --shm-size 1g --rm nkiri/oracle/database:11.2.0.2-sjis
- 別ターミナルで
docker exec
でコンテナに入る
$ docker exec -it oracle
- 以下のコマンドを実行
su -p oracle -c "sqlplus -s / as sysdba" <<EOS > select * from nls_database_parameters where parameter = 'NLS_CHARACTERSET'; > EOS
- 結果
PARAMETER ------------------------------ VALUE -------------------------------------------------------------------------------- NLS_CHARACTERSET JA16SJISTILDE
まとめ
- まぁできるんだけど、ビルドに1時間くらいかかる…
- SYSとSYSTEMのパスワードをビルド時に確定することになるので、公式のやつより若干自由度が下がる
django-rest-frameworkのserializerってどう呼ばれてるんだろう
注釈
目的
- 前に書いたやつ でserializerの中身は読んだ
- でもこのserializerってどこでどう呼ばれてるんだろう
環境
- 前のと同じ
- Serializerの使い方とかは参考記事を参照
結論
mixins.py
で呼ばれてたcreate
とupdate
のそれぞれで
class CreateModelMixin(object): """ Create a model instance. """ def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
class UpdateModelMixin(object): """ Update a model instance. """ def update(self, request, *args, **kwargs): partial = kwargs.pop('partial', False) instance = self.get_object() serializer = self.get_serializer(instance, data=request.data, partial=partial) serializer.is_valid(raise_exception=True) self.perform_update(serializer)
get_serializer
してis_valid
してperform_hogehoge
するっていう流れらしいperform_hogehoge
ではserializer.save()
してる
応用
- POSTとかPUTでAPIを呼ばれた時の挙動を帰るときはviewの
create
メソッドとかupdate
メソッドを上書きしてやればよさげ - DBにデータ書き込みはしないけどPOSTでAPIを提供したいときは↓みたいな感じ?
def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) # ここでserializer.validated_dataを使って何らかの処理をする return Response({'result': 'data'}, status=status.HTTP_200_OK)
参考
django-rest-frameworkのserializerでいろんなメソッドの呼ばれる順番
注釈
- 元記事は以下
- 筆者は同一ですが、こっちに移します
この記事の目的
環境
- djangorestframework: 3.9.1
見るところ
- rest_framework/serializers.py
class Serializer(BaseSerializer)
の辺り- とりあえずここ理解すれば他もなんとかなる?
読む
- renderersとかmixinsとかは後回しでいきなりserializers
to_internal_value(self, data)
471 def to_internal_value(self, data): 472 """ 473 Dict of native values <- Dict of primitive datatypes. 474 """ 475 if not isinstance(data, Mapping): 476 message = self.error_messages['invalid'].format( 477 datatype=type(data).__name__ 478 ) 479 raise ValidationError({ 480 api_settings.NON_FIELD_ERRORS_KEY: [message] 481 }, code='invalid')
- 受け取ったデータ型の検証とか?
483 ret = OrderedDict() 484 errors = OrderedDict() 485 fields = self._writable_fields
ret
: 最終的な返却値errors
: 例外情報fields
:to_internal_value
で処理する対象が格納されている。_writable_fields
は以下の通り。
367 @cached_property 368 def _writable_fields(self): 369 return [ 370 field for field in self.fields.values() if not field.read_only 371 ]
read_only
プロパティがFalse
になっているfieldだけが含まれるらしい。
487 for field in fields: 488 validate_method = getattr(self, 'validate_' + field.field_name, None) 489 primitive_value = field.get_value(data) 490 try: 491 validated_value = field.run_validation(primitive_value)
- fieldの
run_vaildation
を実行
523 def run_validation(self, data=empty): 524 """ 525 Validate a simple representation and return the internal value. 526 527 The provided data may be `empty` if no representation was included 528 in the input. 529 530 May raise `SkipField` if the field should not be included in the 531 validated data. 532 """ 533 (is_empty_value, data) = self.validate_empty_values(data) 534 if is_empty_value: 535 return data 536 value = self.to_internal_value(data) 537 self.run_validators(value) 538 return value
- 渡ってきた値の
to_internal_value
を呼び出し。- ここの処理はまた今度読む
492 if validate_method is not None: 493 validated_value = validate_method(validated_value)
validate_<field名>
という名前でメソッドが宣言されてたらそれを実行する
494 except ValidationError as exc: 495 errors[field.field_name] = exc.detail 496 except DjangoValidationError as exc: 497 errors[field.field_name] = get_error_detail(exc) 498 except SkipField: 499 pass
- 例外が発生したらerrorsに入れる
500 else: 501 set_value(ret, field.source_attrs, validated_value)
- validate_<field名>のメソッドを実行した結果を
ret
に入れる
503 if errors: 504 raise ValidationError(errors)
- 何かしらのエラーがあったら例外発火
506 return ret
to_representation
508 def to_representation(self, instance): 509 """ 510 Object instance -> Dict of primitive datatypes. 511 """ 512 ret = OrderedDict() 513 fields = self._readable_fields
fields
:_readable_fields
で取得_readable_fields
は以下の処理write_only
でないフィールドを取得している
373 @cached_property 374 def _readable_fields(self): 375 return [ 376 field for field in self.fields.values() 377 if not field.write_only 378 ]
514 515 for field in fields: 516 try: 517 attribute = field.get_attribute(instance) 518 except SkipField: 519 continue
instance
から指定のフィールドに格納されているデータを取得
521 # We skip `to_representation` for `None` values so that fields do 522 # not have to explicitly deal with that case. 523 # 524 # For related fields with `use_pk_only_optimization` we need to 525 # resolve the pk value. 526 check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute 527 if check_for_none is None: 528 ret[field.field_name] = None 529 else: 530 ret[field.field_name] = field.to_representation(attribute)
- コメントのとおり…
to_internal_value
と同様に、fieldのto_representation
も順に呼び出される
531 532 return ret
まとめ
- to_internal_value
- 各fieldに対して
to_internal_value
とvalidationを繰り返してる - オブジェクトがネストされているときは、深いところから
to_internal_value
とvalidate_<field名>
が実行されることになりそう
- 各fieldに対して
- to_representation
- 各
field
のto_representation
を呼び出してるくらい?
- 各
field.to_internal_value
とかfield.to_representation
はまた今度読む
Cisco 1812j でOSPFを動かすためのコンフィグ
やったこと
Cisco 1812jを用いてマルチエリアOSPFネットワークを構築。
設定を入れて、実際のパケットの流れとか追ってみた。
構成
Endpoint Device1 --- Router1(C1812j) --- Router2(C1812j) --- Endpoint Device2
Cisco 1812jはFastether0とFastether1のインターフェースを持つ。Router間の接続にFastether 0、RouterとEndpoint Deviceの接続にFastether 1を使用した。
コンフィグ
事前準備
IPアドレス設定
Router1
Router1(config)# interface fastether 0 Router1(config-if)# no shutdown Router1(config-if)# ip address 192.168.0.1 255.255.255.0 Router1(config)# interface fastether 1 Router1(config-if)# no shutdown Router1(config-if)# ip address 192.168.1.1 255.255.255.0
Router2
Router2(config)# interface fastether 0 Router2(config-if)# no shutdown Router2(config-if)# ip address 192.168.0.2 255.255.255.0 Router2(config)# interface fastether 1 Router2(config-if)# no shutdown Router2(config-if)# ip address 192.168.2.1 255.255.255.0
DHCP設定
Router1
Router1(config)# ip dhcp pool network1 Router1(dhcp-config)# network 192.168.1.0 /24 Router(dhcp-config)# default-router 192.168.1.1 Router(dhcp-config)# dns-server 192.168.1.1
Router2
Router2(config)# ip dhcp pool network2 Router2(dhcp-config)# network 192.168.2.0 /24 Router2(dhcp-config)# default-router 192.168.2.1 Router2(dhcp-config)# dns-server 192.168.2.1
本題
OSPF設定
Router1
area 0
Router1(config)# router ospf 1 Router1(config-router)# network 192.168.0.0 0.0.0.255 area 0
area 1
Router1(config)# router ospf 1 Router1(config-router)# network 192.168.1.0 0.0.0.255 area 1
Router2
area 0
Router2(config)# router ospf 1 Router2(config-router)# network 192.168.0.0 0.0.0.255 area 0
area 1
Router2(config)# router ospf 1 Router2(config-router)# network 192.168.2.0 0.0.0.255 area 2
これでコンバージェンスを待つと通信ができるようになる。
仕組みの話は今度調べてまとめる
Ubuntu 14.04でJavaScriptエンジンのSpidermonkeyをビルドして逆アセンブルする
Mozillaのfirefoxに搭載されているJavaScriptエンジンであるSpidermonkeyをビルドする.
今回は実行したJavaScriptコードを逆アセンブルできるようにする.
公式はこちら
準備
# apt-get install autoconf
ソースの取得
# git clone https://github.com/mozilla/gecko-dev.git
ビルド
# cd gecko-dev/js/src # autoconf # mkdir Debug # cd Debug # ../configure --enable-debug --disable-optimize # make
これでdis()とかdissrc()とかできる.
Ubuntu 12.04 LTS で GoogleのJavaScriptエンジンV8をスタンドアロンで動かす
基本的には公式のドキュメントに従って進めるだけ.
今回は実行するJavaScriptを逆アセンブルしたコードを出力させるようにする.
Prepare
$ sudo apt-get install gcc g++ git subversion gcc-multilib g++-multilib
Install depot_tools
$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git $ export PATH="$PATH":`pwd`/depot_tools
Get source code
$ svn co http://v8.googlecode.com/svn/trunk v8
Prerequisite: Installing GYP
$ cd v8 $ make dependencies
Building
$ make -j4 release disassembler=on
Run
$ cd out/xxx.release $ ./d8 hoge.js -print-code