3
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

fetchの呼び出し @Javascript & Node.js 実験室

Last updated at Posted at 2020-01-12

fetchは、HTTP呼び出しで標準的に使っていますが、Content-Typeに従った呼び出し方をいつも忘れてしまうので、備忘録として残しておきます。

実動作の確認のためのソースコードを以下のGitHubに上げておきました。
 https://github.com/poruruba/fetch_laboratory

Javascriptからの呼び出しを中心に示しますが、同じコードがそのままNode.jsでも動作します。
ちょっとだけ、Lambdaでの動作も示しておきます。

呼び出し方法の種類

今回扱う呼び出し方法は以下の通りです。

・Get呼び出し
 HTMLのページ取得でおなじみです。パラメータをQueryStringに指定します。例えば、以下のような呼び出しです。
 http://localhost:10080/api?param1=abcd

・Post(Content-Type: application/json)呼び出し
 パラメータをBody部にJSONで指定するWebAPI呼び出しでは一番一般的ですね。

・Post(Content-Type: application/x-www-form-urlencoded)呼び出し
 フォームでSumitするときの方法です。例えば、以下のような呼び出しです。
 <form action="http://localhost:10080/post-urlencoded" method="post">

・Post(Content-Type: multipart/form-data)呼び出し
 フォームでmultipart指定でSumitするときの方法です。例えば、以下のような呼び出しです。
 <form action="http://localhost:10080/post-formdata" method="post" enctype="multipart/form-data">

Javascriptの場合

まずは、Javascriptから。

Post(Content-Type: application/json)呼び出し

function do_post(url, body) {
  const headers = new Headers({ "Content-Type": "application/json" });

  return fetch(url, {
      method: 'POST',
      body: JSON.stringify(body),
      headers: headers
    })
    .then((response) => {
      if (!response.ok)
        throw new Error('status is not 200');
      return response.json();
    });
}

Javascriptから送信
image.png

Post(Content-Type: application/x-www-form-urlencoded)呼び出し


function do_post_urlencoded(url, params) {
  const headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
  var body = new URLSearchParams(params);

  return fetch(url, {
      method: 'POST',
      body: body,
      headers: headers
    })
    .then((response) => {
      if (!response.ok)
        throw new Error('status is not 200');
      return response.json();
    })
}

フォームから送信した場合
image.png

Javascriptから送信した場合
image.png

Post(Content-Type: multipart/form-data)呼び出し

function do_post_formdata(url, params){
    var body = Object.entries(params).reduce( (l, [k,v])=> { l.append(k, v); return l; }, new FormData());

  return fetch(url, {
      method : 'POST',
      body: body,
  })
  .then((response) => {
    if( !response.ok )
      throw new Error('status is not 200');
    return response.json();
  });
}

フォームから送信した場合
image.png

Javascriptから送信した場合
image.png

GET呼び出し

function do_get(url, qs) {
  var params = new URLSearchParams(qs);

  var params_str = params.toString();
  var postfix = (params_str == "") ? "" : ((url.indexOf('?') >= 0) ? ('&' + params_str) : ('?' + params_str));
  return fetch(url + postfix, {
      method: 'GET',
    })
    .then((response) => {
      if (!response.ok)
        throw new Error('status is not 200');
      return response.json();
    });
}

フォームから送信した場合
image.png

Javascriptから送信した場合
image.png

まとめ

まとめるとこんな感じ

// input: url, method, qs, body, params, response_type, content_type, token, api_key
async function do_http(input){
  const method = input.method ? input.method : "POST";
  const content_type = input.content_type ? input.content_type : "application/json";
  const response_type = input.response_type ? input.response_type : "json";

  const headers = new Headers();
  if( content_type != "multipart/form-data" )
    headers.append("Content-Type", content_type);
  if( input.token )
    headers.append("Authorization", "Bearer " + input.token);
  if( input.api_key )
    headers.append("x-api-key", input.api_key);

  let body;
  if( content_type == "application/json" ){
    body = JSON.stringify(input.body);
  }else if( content_type == "application/x-www-form-urlencoded"){
    body = new URLSearchParams(input.params);
  }else if( content_type == "multipart/form-data"){
    body = Object.entries(input.params).reduce((l, [k, v]) => { l.append(k, v); return l; }, new FormData());
  }

  const params = new URLSearchParams(input.qs);
  var params_str = params.toString();
  var postfix = (params_str == "") ? "" : ((input.url.indexOf('?') >= 0) ? ('&' + params_str) : ('?' + params_str));

  return fetch(input.url + postfix, {
    method: method,
    body: body,
    headers: headers
  })
  .then((response) => {
    if (!response.ok)
      throw new Error('status is not 200');

    if( response_type == "json" )
      return response.json();
    else if( response_type == 'blob')
      return response.blob();
    else if( response_type == 'file'){
      const disposition = response.headers.get('Content-Disposition');
      let filename = "";
      if( disposition ){
        filename = disposition.split(/;(.+)/)[1].split(/=(.+)/)[1];
        if (filename.toLowerCase().startsWith("utf-8''"))
            filename = decodeURIComponent(filename.replace(/utf-8''/i, ''));
        else
            filename = filename.replace(/['"]/g, '');
      }
      return response.blob()
      .then(blob =>{
        return new File([blob], filename, { type: blob.type })      
      });
    }
    else if( response_type == 'binary')
      return response.arrayBuffer();
    else // response_type == "text" )
      return response.text();
  });
}

Node.jsの場合

Node.jsでも呼び出し方は同じです。
ポイントは、以下のnpmモジュールを利用するところです。

・node-fetch
 https://github.com/node-fetch/node-fetch
・form-data
 https://github.com/form-data/form-data

以下を先頭に記述することで、ほぼJavascriptと同じ記述で同じ動作となります。

const FormData = require('form-data');
const { URL, URLSearchParams } = require('url');
const fetch = require('node-fetch');
const Headers = fetch.Headers;

実験のためのサンプルページ

以下のようなページから、各呼び出し方法を確認します。
F11を押して、DevToolsを開くと、通信内容を詳しく見ることができます。

image.png

先ほど示した関数群を呼び出しているだけでして、詳細はGitHubのソースコードを参照ください。
参考までに、リクエストを受け付けるサーバ側のソースも示しておきます。

index.js
'use strict';

const HELPER_BASE = process.env.HELPER_BASE || '../../helpers/';
const Response = require(HELPER_BASE + 'response');
const Redirect = require(HELPER_BASE + 'redirect');

// Lambda+API Gatewayの場合に必要
//const { URLSearchParams } = require('url');
//const multipart = require('aws-lambda-multipart-parser');

exports.handler = async (event, context, callback) => {
  if( event.path == '/post-json'){
    console.log(event.body);

    var body = JSON.parse(event.body);
    var response = {
      path : event.path,
      param: {
        param1: body.param1,
        param2: body.param2,
      }
    };
    return new Response(response);
  }else
  if( event.path == '/post-urlencoded'){
    // Lambda+API Gatewayの場合はこちら
    //var body = {};
    //for( var pair of new URLSearchParams(event.body).entries() ) body[pair[0]] = pair[1];
    // swagger_nodeの場合はこちら
    var body = JSON.parse(event.body);

    var response = {
      path : event.path,
      param: {
        param1: body.param1,
        param2: body.param2,
      }
    };
    return new Response(response);
  }else
  if( event.path == '/post-formdata' ){
    // Lambda+API Gatewayの場合はこちら
    //var body = multipart.parse(event);
    // swagger_nodeの場合はこちら
    var body = JSON.parse(event.body);

    console.log(body);
    var response = {
      path : event.path,
      param: {
        param1: body.param1,
        param2: body.param2,
      }
    };
    return new Response(response);
  }else
  if( event.path == '/get-qs'){
    console.log(event.queryStringParameters);

    var response = {
      path : event.path,
      param: {
        param1: event.queryStringParameters.param1,
        param2: event.queryStringParameters.param2,
      }
    };
    return new Response(response);
  }
};

一方で、Node.jsでの呼び出しも確認したかったので、Node.js側の確認のためのソースも示しておきます。
動作としては、いったん、サーバ側でリクエストを受け付けたのち、パラメータで指定されたbody.typeの値によって、各呼び出し方法に従った呼び出しをNode.jsで行っています。
コードを見ていただくと、Javascriptでの呼び出しと同じであることがわかります。


'use strict';

const HELPER_BASE = process.env.HELPER_BASE || '../../helpers/';
const Response = require(HELPER_BASE + 'response');
const Redirect = require(HELPER_BASE + 'redirect');

const FormData = require('form-data');
const fetch = require('node-fetch');
const { URL, URLSearchParams } = require('url');
const Headers = fetch.Headers;

const baseurl = 'http://localhost:10080';

exports.handler = async (event, context, callback) => {
  if( event.path == '/node'){
    console.log(event.body);

    var body = JSON.parse(event.body);
    if( body.type == "post-json"){
      var params = {
        param1: body.param1,
        param2: body.param2,
      };
      return do_post(baseurl + '/post-json', params)
        .then(json => {
          console.log(json);
          return new Response({ type: body.type, resposne: json });
        })
        .catch(error => {
          console.log(error);
          return new Response(error);
        });
    }else
    if( body.type == "post-urlencoded"){
      var params = {
        param1: body.param1,
        param2: body.param2,
      };
      return do_post_urlencoded(baseurl + '/post-urlencoded', params)
        .then(json => {
          console.log(json);
          return new Response({ type: body.type, resposne: json });
        })
        .catch(error => {
          console.log(error);
          return new Response(error);
        });
    }else
    if( body.type == "post-formdata"){
      var params = {
        param1: body.param1,
        param2: body.param2,
      };
      return do_post_formdata(baseurl + '/post-formdata', params)
        .then(json => {
          console.log(json);
          return new Response({ type: body.type, resposne: json });
        })
        .catch(error => {
          console.log(error);
          return new Response(error);
        });
    }else
    if( body.type == 'get-qs'){
      var qs = {
        param1: body.param1,
        param2: body.param2,
      }
      return do_get(baseurl + '/get-qs', qs)
        .then(json => {
          console.log(json);
          return new Response({ type: body.type, resposne: json });
        })
        .catch(error => {
          console.log(error);
          return new Response(error);
        });
    }

    var response = {
      type : body.type,
      param: {
        param1: body.param1,
        param2: body.param2,
      }
    };
    return new Response(response);
  }
};


function do_post(url, body) {
  const headers = new Headers({ "Content-Type": "application/json; charset=utf-8" });

  return fetch(url, {
      method: 'POST',
      body: JSON.stringify(body),
      headers: headers
    })
    .then((response) => {
      if (!response.ok)
        throw new Error('status is not 200');
      return response.json();
    });
}

function do_get(url, qs) {
  var params = new URLSearchParams(qs);

  return fetch(url + `?` + params.toString(), {
      method: 'GET',
    })
    .then((response) => {
      if (!response.ok)
        throw new Error('status is not 200');
      return response.json();
    });
}

function do_post_urlencoded(url, params) {
  const headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
  var body = new URLSearchParams(params);

  return fetch(url, {
      method: 'POST',
      body: body,
      headers: headers
    })
    .then((response) => {
      if (!response.ok)
        throw new Error('status is not 200');
      return response.json();
    })
}

function do_post_formdata(url, params) {
  var body = Object.entries(params).reduce((l, [k, v]) => {
    l.append(k, v);
    return l;
  }, new FormData());

  return fetch(url, {
      method: 'POST',
      body: body,
    })
    .then((response) => {
      if (!response.ok)
        throw new Error('status is not 200');
      return response.json();
    });
}

補足

※レスポンスの型
最近レスポンスはJSONで返されることが多くなっていますので、すべてJSONで返ってくることを想定しています。もし、単なるテキストで返ってくる場合には以下の部分を変更します。

(変更前) return response.json();

(変更後) return response.text();

以上

3
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?