angular.jsはdataがundefinedだった場合にContent-Type headerを消す

はじめに

requestのcontent typeをみて、responseの形式を変えたいと思うことがありました。 そのためにheadersを変えてrequestを投げてみたのですが、どうも上手くcontent typeが付かない。 なぜだろうと思って調べてみたのでした。

実験環境

とりあえず、挙動を調べるためにテキトウな実験環境を作る。

content typeを確認するためのserverの作成

とりあえず、以下のような挙動をするserverを書く。httpコマンドはhttpieをinstallしていないとないかもしれません。 requestのheaderの内容を返すserverです。めんどくさかったのでpythonで書きました。

$ python server.py &
Serving on port 8000...
$ http http://localhost:8000 "Content-Type:application/json" | grep CONTENT
  "CONTENT_LENGTH": "",
  "CONTENT_TYPE": "application/json",
$ http http://localhost:8000 "Content-Type:text/html" | grep CONTENT
  "CONTENT_LENGTH": "",
  "CONTENT_TYPE": "text/html",
$ http http://localhost:8000 | grep CONTENT
  "CONTENT_LENGTH": "",
  "CONTENT_TYPE": "text/plain",

渡したcontent typeが適切に返っていそうです。

code

# -*- coding:utf-8 -*-
import logging
import json
logger = logging.getLogger(__name__)

from wsgiref.util import setup_testing_defaults
from wsgiref.simple_server import make_server


def simple_app(environ, start_response):
    setup_testing_defaults(environ)

    status = '200 OK'
    headers = [('Content-type', 'text/json; charset=utf-8')]

    start_response(status, headers)
    data = {k: v for k, v in environ.items() if isinstance(v, (int, float, str, bool))}
    ret = bytes(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
    return [ret]

httpd = make_server('', 8000, simple_app)
print("Serving on port 8000...")
httpd.serve_forever()

angularのAPI越しにrequestを投げてみる

node.jsで動くようなclientも作成しておきます。以下の様な挙動です。

node app.js
input: {}
Content type: text/plain
input: {"headers":{"Content-Type":"application/json"}}
Content type: text/plain
input: {"headers":{"Content-Type":"application/json"}, "data":""}
Content type: application/json

input部分に着目して欲しいのですが、headersにcontent typeを与えても無視されています。 一方でdataオプションの値を追加するとcontent typeが認識されるようです。

code

require('../setup')(function(angular){
  'use strict';

  function callAPI($http){
    var data = {
      method: "GET",
      url: "http://localhost:8000",
    };
    return function(opts){
      console.log("input: %s", JSON.stringify(opts));
      return $http(Object.assign({}, data, opts))
        .then(function(response){
          console.log("Content type: %s", response.data.CONTENT_TYPE);
        }).catch(function(err){
          console.log("!");
          console.log(err);
        });
    };
  }
  callAPI.$inject = ["$http"];

  angular.module("app", [])
    .factory("callAPI", callAPI);

  var inj = angular.injector(["app", "ng"]);
  var api = inj.get("callAPI");
  api({}).then(function(){
    return api({headers: {"Content-Type": "application/json"}});
  }).then(function(){
    return api({headers: {"Content-Type": "application/json"}, data: ""});
  });
});

angular.jsのコードを見てみる

必要となりそうなコードの部分だけ抜粋。たしかにdataがundefinedだった場合に、わざわざdeleteしている。

/**
 * @license AngularJS v1.4.8
 * (c) 2010-2015 Google, Inc. http://angularjs.org
 * License: MIT
 */

// snip..

    function $http(requestConfig) {

// snip..

      var serverRequest = function(config) {
        var headers = config.headers;
        var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);

        // strip content-type if data is undefined
        if (isUndefined(reqData)) {
          forEach(headers, function(value, header) {
            if (lowercase(header) === 'content-type') {
                delete headers[header];
            }
          });
        }

gist

https://gist.github.com/podhmo/38bba130c7a5ed3121b7