はじめに
4/1にリリースされた国土交通省の「不動産情報ライブラリAPI」とSpringの相性がすこぶる悪く、色々な試行錯誤を経て解決に辿り着いたので記事にしました。Springわかんなくても読める・・・かも?
この記事で伝えたいこと
- 外部APIが文字化けしたときに問題解決までどうやってたどり着いたか
- Springで圧縮された形式のAPIレスポンスを受け取る方法
後者だけ知りたい方は「解決策」まで飛ばしてください!
経緯
4月になってエンジニア2年目になった私は、3月に引き続きSpringのシステムの結合テストを行っていました。システムを触っていると、エリア検索等に用いていた市区町村一覧取得機能が突如エラーを吐くようになっていました。エラーにAPIっぽいのが出てたのでそれをクリックして遷移すると・・・
思わずhuh?が飛び出そうになりましたが、リリース前で良かった〜と思うことにしました。重ならなくて本当に良かった・・・。
ということで急にライブラリが一新されてしまい、新APIの使用には申請が必要とのことだったので申請しました。受理されるまでにおよそ1週間かかりました。本当にリリース前で良かった・・・。
APIが文字化けした
申請も通り、早速使ってみようとSpringでAPIを叩くプログラムを作成しました。APIの仕様書に書いてある通り、リクエストヘッダの”Ocp-Apim-Subscription-Key”にAPIキーを設定してリクエストを送るコードを書きました。
public class SearchCityUtil {
private final String apiKey = "myApiKey";
@Autowired
private RestTemplate restTemplate;
public List<String> fetchCities(String prefectureCode) {
// 市区町村一覧取得API
String url = "https://www.reinfolib.mlit.go.jp/ex-api/external/XIT002?area=" + prefectureCode;
HttpHeaders headers = new HttpHeaders();
headers.set("Ocp-Apim-Subscription-Key", apiKey);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
String responseBody = response.getBody();
...
}
}
ちゃんと返ってくるかな〜
?????
こうして長い旅が始まりました。
試したこと
色々Springでいじってみたが解決せず
まずは、APIを叩くときの基本的な(といってもよくわかっていない)部分をいじってみました。
- エンコーディングを色々変えてみる
- Content-Typeをapplication/jsonに設定してみる
- Content-Typeの中にエンコーディングを含めてみる
などなど・・・
しかし一向に解決しなかったので、もしかしてAPIがおかしいのでは?という可能性に辿り着きました。
ですが、今のままではSpringとAPIのどちらが悪いのか判断がつきません。
そこで問題の切り分けのためにPostman
という別ツールを試してみることにしました。
PostmanでAPIを叩いてみた
このためだけにインストールするのは腰が重かったのですが、使ってみると便利でとてもいいツールでした。
忘れずにリクエストヘッダにAPIキーをセットして叩いてみると・・・
綺麗な結果が返ってきました!国土交通省さん、疑ってごめんなさい。
でもこれで、Spring側に問題がありそうなことがわかりました。一歩前進です。
しかしここで、「Spring 文字化け API」などとググっても「日本語のみが文字化けする」のようなものしか出てきませんでした。
困った私は、ChatGPTくんの力を借りることにしました。
ChatGPTに聞いてみた
といっても実装の内容について質問してもなかなかいい返事は得られませんでした。
そこで、文字化けそのものを投下してみました。Google検索では絶対にできない使い方です。すると・・・
gzip
というキーワードが出てきました。当時の私にとってはなんのことやらサッパリという状態でしたが、今思うと名推理すぎました。やっぱりお前すごいよ。
ただこのときは、散々的外れなことを言われていたためGPTくんのことを全然信用していませんでした。
もう少し色々調べているうちに、あることに気がつきました。
よく見たらPostman
のリクエストヘッダにもgzip
って書いてあるやんけ!!
この時点で8割くらい確信は得ていましたが、念の為コード上でも確かめてみると、確かにresponseHeader
の中にgzip
と書かれていました。さすがに確定か・・・
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
String responseBody = response.getBody();
// この中にgzipの文字が!
HttpHeader responseHeader = response.getHeaders();
文字化けの正体はgzipだった
原因がほぼ確になったので、あとは「Spring API gzip」などで検索しました。
すると、確かにSpring
のRestTemplate
はデフォルトでgzip
を解凍できないとの記述が・・・!
https://tech.excite.co.jp/entry/2021/11/01/133452
代わりにRestTemplateBuilder
を用いてRestTemplate
を生成したところ、無事に解決しました!!!
今回の問題を通して学んだこと
- 問題の切り分けは大事
リリースされたばかりのものということもあったので、最初はAPI自体に問題がある可能性を捨てきれていませんでした。ですが、別ツールを使って叩いてみることでその可能性を排除でき、Springの問題として扱うことができました。
今回はPostman
のインストールというちょっと面倒なことをしましたが、まさに急がば回れという言葉の通りでした。
- 不確実な可能性にすぐに飛び付かずに検証する
途中からgzip
っぽいなーという匂いはプンプンしていたのですが、ソースがChatGPTだったこともあり、グッと堪えて証拠集めに走りました。今回は当たっていたから良かったのですが、外れていた可能性もあったので、この姿勢は続けていきたいなと思いました。
専門的になるにつれてGPTくんもそれっぽいことしか言わなくなってくるのは体感としてありますねw
解決策
改めて、不動産ライブラリAPIのレスポンスは、実はgzip
という圧縮形式で返ってきます。
他のフレームワークだと自動解凍してくれることも多いそうですが、実はSpringでよく紹介されているRestTemplate
はデフォルトではgzipに対応していません。
private RestTemplate restTemplate = new RestTemplate();
代わりに、RestTemplateBuilderを用いてRestTemplateを生成してあげると、gzipを解凍できるようになります。
private RestTemplate restTemplate;
// コンストラクタ
public SearchCityUtil(RestTemplateBuilder restTemplateBuilder) {
// APIが圧縮されているため、restTemplateBuilderを使って解凍する
this.restTemplate = restTemplateBuilder
.setConnectTimeout(Duration.ofSeconds(10))
.setReadTimeout(Duration.ofSeconds(10))
.build();
}
ちょっとだけ補足
私自身も完全に理解できてはいませんが一応理屈の説明を残しておきます。
まずRestTemplate
について。
このクラスはClientHttpRequestFactory
というインターフェースを用いて、様々なHTTPライブラリにリクエストを送ることを可能にします。
で、このClientHttpRequestFactory
のデフォルトの実装がSimpleClientHttpRequestFactory
というクラスだそうですが、実はこやつがgzipに対応していません。なんてこった・・・
代わりにRestTemplateBuilder
を使用してRestTemplate
を生成すると、実装がHttpComponentsClientHttpRequestFactory
という、gzip対応しているクラスにしてくれます。やったね。
何のこっちゃという感じかもしれませんが、以下のメソッドで実際のClientHttpRequestFactory
を取得してみるとわかりやすいかもしれません。
ClientHttpRequestFactory requestFactory = restTemplate.getRequestFactory();
また、RestTemplateBuilder
を使用せずとも、手動で明示的にHttpComponentsClientHttpRequestFactory
を指定する方法もあるみたいです。そっちの方がいい気もしてきましたが、今回は割愛します。
終わりに
今回は以下のような理由から記事を書きました。
- 不動産情報ライブラリAPIという新しいものについての問題だったため
- 問題解決が綺麗にできたため
ちなみにこの一連の流れで半日(4時間)持っていかれました。4時間で済んだと考えるべきな気はしますが、結構な気力を持っていかれました・・・