見出し画像

Django、カスタムヘッダー + ミドルウェアでテンプレートとAPI用レスポンス(Json)を出し分け!

こんにちは、PWAの勉強会で「PWAあるある」とか発表しておきながら、脱PWAからのネイティブアプリ開発資金クラウドファンディングの話(宣伝ではない)までしてしまった原田です。

コードは同じでAPIだけJSONで返したい


ネイティブアプリにするということでレスポンスをJSONにしないといけません。
しかし今はDjangoのテンプレートエンジンを使っているので、レスポンスはHttpResponseです。
これはJSONシリアライズできません

そこで、Web用のHttpResponseはそのままに、APIの場合はViewで入れた変数の中身は同じでJsonResponseで返す、「出し分け」をしたいと考えました。
かなり簡単にできたので、おすすめです。
ちなみに僕はDjangoRestFrameworkはまだ使ってないので、シリアライズ可能な形にするのは手動でやってます…。

API用か否かの判別

API用の判別は何パターンかあると思います。

URLを変える
 https://sample.com/api/...
URLパラメタを変える
 https://sample.com?=parameter
リクエストヘッダを変える
 HTTP_ほにゃらら:true

僕は最後のリクエストヘッダを変えることにしました。
出し分けの方法が見えない方がかっこいいからです。
まぁURL自体を変えるのは超面倒だし、URLパラメタをいちいち追加するのもダルそうなんで、アプリからのリクエストには全部カスタムリクエストヘッダつける、とするのが楽でしょう。

リクエストヘッダ名はHTTP_APIにしました。
"HTTP_API" header で調べたら4350件のヒット。
少ねえな。
まぁいっか。

curlコマンドで調べる時は、

curl -H 'API:true' {URL}

でOK。
-Hがヘッダーのオプション。
curlの方でAPIとしておけば、Djangoの方では、HTTPというprefixが自動でついて、HTTP_APIというヘッダーとして扱われます。

Django側、ミドルウェアの実装

全てのリクエストに対して、ヘッダーで分岐させて同じ処理を行いたいので、ミドルウェアを使います。
コードはこんな感じ(myproject/myproject/MyMiddleware)。
settings.pyと同じディレクトリにおきました。

import json
from django.http import JsonResponse

class APIMiddleware:
  
  def __init__(self, get_response):
       self.get_response = get_response
   
  def __call__(self, request):
       
       response = self.get_response(request)
   
      if not "HTTP_API" in request.META:
           return response
   
      content = response.context_data
       json_data = {}
   
      for key, value in content.items():
           try:
               j = json.dumps({ key:value}) 
               json_data.update({key:value})
           except e: 
               pass
       
       return JsonResponse(json_data)  

メソッド1つ目は、サーバー起動時しか発動しないのでテンプレ通り。
2つ目のメソッド__call__の最初のコード、

response = self.get_response(request)

のあとは、もうViewを通った後です。
なので、このresponseはHttpResponseオブジェクトです。

続くコードで、request.METAにカスタムヘッダーHTTP_APIが入ってなければそのままHttpResponseを返すようにしています。

で、API用だった場合は、HttpsResponseから、シリアライズできるものだけ辞書に入れていって、JsonResponseで返します。
json.dumps()はシリアライズできるかどうか確かめるために使っているだけで返り値を使ったりはしていません(なので実はjに代入する必要もなし)。

もともとシリアライズできるものだけ変数に入れとけばシリアライズできるか試す必要ないじゃん!って思うかもですが、シリアライズできないViewオブジェクトがHttpResponseに入っている(Viewオブジェクトとかが自動で入ってくる)ので、それを弾くためにやっています。
他にもシリアライズできないものが入ってる可能性もあるので、保険としても必要かなと思います。

てことで、これにて一件落着!

ミドルウェアの登録

と思いきや、ミドルウェアの登録をしとかないとworkしません。
settings.pyのMIDDLEWARE = の最後に、作ったミドルウェアを追加してください。

'myproject.MyMiddleware.APIMiddleware'

を追加でOKです!(myprojectのところは、settings.pyのあるディレクトリ名を指定したください)

いやーミドルウェア便利ですね。
ただ、あんまり使いすぎると処理遅くなっちゃうので適度に。
ちなみに僕はログ取るときも自作のミドルウェア使ってます。
get_responseの後と前で、ページごとのリクエスト処理の時間も測れますし。

ミドルウェアとかカスタムヘッダーとか使うと、自分のシステム感が出てきて楽しいですね。

今日は以上です!

この記事が気に入ったらサポートをしてみませんか?