HTTPヘッダーのContent-Typeを自在に扱う

f:id:golazooo23:20210315183439p:plain

目次

  • はじめに
  • HTTPヘッダーとは
  • Content-Typeの概要
  • 検証内容
  • おわりに

はじめに

こんにちは、スタメンでエンジニアをしている手嶋です。普段はReact+TypeScriptでフロントエンドを開発したり、RailsでAPIを作成しています。クライアントサイドからサーバーサイドへリクエストするに当たり、HTTPヘッダーのContent-Typeを柔軟に変える事でリクエストの記述をシンプルに出来たので、今回紹介したいと思います。

HTTPヘッダーとは

  • まずHTTPヘッダーについてですが、以下のように定義されています。

HTTPヘッダーは、要求または応答に関する追加のコンテキスト及びメタデータを渡すHTTP要求または応答のフィールド

HTTPヘッダーは以下3つにカテゴライズされる

リクエストヘッダー:フェッチするリソースまたはクライアント自体に関する詳細情報を含むヘッダー。 応答ヘッダー:場所やサーバー自体(名前、バージョンなど)など、応答に関する追加情報を含むヘッダー。 表現メタデータヘッダー:メッセージ本文のリソースに関するメタデータ(言語、長さ、メディアタイプなど)

  • Content-Typeはリクエストボディのメディアタイプを指定する役割を持つので、表現メタデータヘッダーに該当します。

Content-Typeの概要

  • 上述の通り、Content-Typeはリクエスト時にメディアタイプを指定する役割を果たします。
  • メディアタイプは、MIMEタイプや要素タイプとも言われ、インターネット上で転送されるコンテンツの形式を表現する識別子を表します。
  • 具体的な種類の例として以下が挙げられます。ファイルは「形式」と適宜読み換えてください。
MIMEタイプ 文書の種類
text/plain テキストファイル
text/csv CSVファイル
text/html HTMLファイル
text/css CSSファイル
text/javascript JavaScriptファイル
application/json JSONファイル
application/pdf PDFファイル
image/jpeg JPEGファイル(.jpg, .jpeg)
image/png PNGファイル
image/gif GIFファイル
image/svg+xml SVGファイル
application/zip Zipファイル
video/mpeg MPEGファイル(動画)

検証内容

  • 今回検証した内容は以下です。
  • 前提としてReactアプリケーションはPOST(PATCH)パラメータ(params)をオブジェクトとして管理しています。そしてAPIにリクエストするファイルでは以下のようにパラメータを展開します。
  • パラメータは全てが必須項目ではなく、存在する場合のみリクエストに含める想定です。

改善前

// type
type UserParamsType = {
  name: string
  email: string
  address: string
  phone: number
  gender: string
}

// user更新用の関数。 別関数にエンドポイントurlとリクエストbodyを渡す
export const requestUpdateUser = async (
  userId: number,
  params: UserParamsType
) => {
// エンドポイント
  const url = `api/v1/user/${userId}`
// パラメータ生成  
  const {
    name,
    email,
    address,
    phone,
    gender,
  } = params
  let body = ''
  if (name) body += `&[name]=${encodeURIComponent(name)}`
  if (email) body += `&[email]=${encodeURIComponent(email)}`
  if (address) body += `&[address]=${encodeURIComponent(address)}`
  if (phone) body += `&[phone]=${encodeURIComponent(phone)}`
  if (gender) body += `&[gender]=${encodeURIComponent(gender)}`

  const response = await fetchPatchTemplate(url, body)
  return response
}

// HEADERS
// Content-Typeにはapplication/x-www-form-urlencoded; charset=utf-8を指定
const HEADERS = {
  Accept: 'application/json',
  'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
}

// apiを叩く関数
export const fetchPatchTemplate = async (url: string, body: string ) => {
  try {
    const response = await fetch(url, {
      credentials: 'same-origin',
      method: 'PATCH',
      headers: HEADERS,
      body,
    })
    if (!response.ok) {
      throw Error(response.statusText)
    }
    const resJson = await response.json()
    return { payload: resJson }
  } catch (error) {
    return { error: 'エラーメッセージ' }
  }
}
  • 上記の通りContent-Typeにはapplication/x-www-form-urlencoded; charset=utf-8を指定しています。
  • このTypeは、「キーと値が '=' を挟んで組になり、 '&' で区切られてエンコードされる」という特徴を持ちます。
  • よってこのTypeを指定した場合は、上記の記述でparamsを生成し、リクエストbodyに含める事ができます。

改善案

  • しかし、上記の記述ではparamsの数だけ展開の記述をする回数が増えてしまいます。
  • その場合は、以下のようにparams展開部分ContentTypeを書き換える事で記述量を減らす事が可能です。
  • params展開部分 => JSON.stringify(params)
  • JSON.stringify() メソッドは、あるJavaScript のオブジェクトや値をJSON文字列に変換するメソッドです。
  • ContentType => 'application/json'
  • application/jsonに変更しJSON形式を扱えるよう変更します。
// type
type UserParamsType = {
  name: string
  email: string
  address: string
  phone: number
  gender: string
}

// user更新用の関数。 別関数にエンドポイントurlとリクエストbodyを渡す
export const requestUpdateUser = async (
  userId: number,
  params: UserParamsType
) => {
  // エンドポイント
  const url = `api/v1/user/${userId}`
  // オブジェクト形式のparamsをJSON.stringifyの引数に渡しパラメータ生成
  const body = JSON.stringify(params)

  const response = await fetchPatchTemplate(url, body)
  return response
}

// HEADERS
// Content-Typeにはapplication/jsonを指定
const HEADERS = {
  Accept: 'application/json',
  'Content-Type': 'application/json'
}

// apiを叩く関数
export const fetchPatchTemplate = async (url: string, body: string ) => {
  try {
    const response = await fetch(url, {
      credentials: 'same-origin',
      method: 'PATCH',
      headers: HEADERS,
      body,
    })
    if (!response.ok) {
      throw Error(response.statusText)
    }
    const resJson = await response.json()
    return { payload: resJson }
  } catch (error) {
    return { error: 'エラーメッセージ' }
  }
}

おわりに

今回はHTTP通信におけるヘッダー及びContent-Typeについて紹介させていただきました。 paramsの量にもよりますが、クライアントの記述を大幅に減らすことができる場合もあるので、これからもContent-Typeを柔軟に扱っていければと思います。 今回の内容が少しでも参考となれば幸いです。

スタメンでは一緒にプロダクト開発を進めてくれる仲間を募集しています! 興味を持っていただいた方は、是非下記の募集ページを御覧ください。

Webアプリケーションエンジニア募集ページ

参考