CORSエラーで行った対応

# 概要
簡単なツール作成を通してAngularの使い方を勉強をしています。
その過程で、APIと連携させようとすると、下記のようなCORSのエラーが発生したので
私が行った対応を記載します。

```
クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、http://*****/api/company/get_detail.html にあるリモートリソースの読み込みは拒否されます (理由: CORS ヘッダー ‘Access-Control-Allow-Origin’ が足りない)。
```

# 前提
1. 作成しているツールは、`http://localhost:4200`で動作している
2. API（http://*****/api/company/get_detail.html）へのアクセスに成功すると、会社情報がJSON形式で返ってくる
3. APIはJSONPに対応している

# 原因
1. `http://localhost:4200`で動作しているツールから、別ドメインで動いているAPIにアクセスしようとしたため、
同一生成元ポリシーに反しており、リクエストが制限された。
2. `http://localhost:4200`からのクロスドメインアクセスがサーバ側で許可されていなかった。

- エラーが発生していた時のコード

```js:company.service.ts
export class CompanyService <T> {
  constructor(private http: Http) {}
  getCompanyDetail() : Promise<T> {
    let sendUrl = 'http://*****/api/company/get_detail.html';
    let headers = new Headers({ 'Content-Type': 'application/json' });
    let options = new RequestOptions({ headers: headers });
    return this.http.post(sendUrl , {}, options).map(res => res.json()).toPromise();
  }
}
```

# 私が行った対応
## 対応１ JSONPを使ってみる
AngularにJsonpクラスがあったので、使い方を知るためにJSONPでアクセスするようにしてみました。

```js:company.service.ts
export class CompanyService <T> {
  constructor(private jsonp: Jsonp) {}
  getCompanyDetail() : Promise<T> {
    // コールバック関数は固定で「JSONP_CALLBACK」を指定
    let sendUrl = 'http://*****/api/company/get_detail.html?callback=JSONP_CALLBACK';
    return this.jsonp.get(sendUrl).map(res => res.json()).toPromise();
  }
}
```

```js:company.ts
export class CompanyComponent implements OnInit {
  constructor(private companyService : CompanyService<{}>) {}
  ngOnInit() {
    this.getCompany();
  }
  getCompany() {
    this.companyService.getCompanyDetail()
    .then(res => console.log(res))
    .catch(err => console.log(err));
  }
}
```

JSONPを使うことで、クロスドメインの制約を受けずに手軽に実装できますが、
セキュリティなどの問題もあるので、JSONPを使う場合は一般に公開しても問題ないAPIへのアクセスにのみ使用した方が良さそうです。

参考：[JSONPでのクロスドメインアクセス](http://gihyo.jp/dev/serial/01/web20sec/0003)

## 対応２ CORSを利用してみる
参考：[HTTP アクセス制御 (CORS)](https://developer.mozilla.org/ja/docs/Web/HTTP/HTTP_access_control)

クロスドメインアクセスを許可する設定をサーバ側に追加します。
ツール側は、Httpクラスを使うように修正。（JSONPに変更する前の状態に戻しました。）

```js:company.service.ts
export class CompanyService <T> {
  constructor(private http: Http) {}
  getCompanyDetail() : Promise<T> {
    let sendUrl = 'http://*****/api/company/get_detail.html';
    let headers = new Headers({ 'Content-Type': 'application/json' });
    let options = new RequestOptions({ headers: headers });
    return this.http.post(sendUrl , {}, options).map(res => res.json()).toPromise();
  }
}
```

### (1) Access-Control-Allow-Originを追加
```apache:conf
<Location /api/company>
  <IfModule mod_headers.c>
     # アクセスを許可するURLを指定
     Header set Access-Control-Allow-Origin "http://localhost:4200"
  </IfModule>
</Location>
```

この状態でAPIへアクセスしてみると、今度は下記のようなエラーが発生しました。

```
クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、http://*****/api/company/get_detail.html にあるリモートリソースの読み込みは拒否されます (理由: CORS プリフライトチャンネルからの CORS ヘッダー ‘Access-Control-Allow-Headers’ にトークン ‘content-type’ が足りない)。
```

Access-Control-Allow-Headersの設定が必要なようです。

### (2) Access-Control-Allow-Headersを追加

```apache:conf
<Location /api/company>
  <IfModule mod_headers.c>
     # アクセスを許可するURLを指定
     Header set Access-Control-Allow-Origin "http://localhost:4200"
     # 許可するリクエストヘッダの種類を指定
     Header set Access-Control-Allow-Headers "Content-Type"
  </IfModule>
</Location>
```

この設定を追加すると、APIへアクセス出来るようになりました。
ついでなので、許可するHTTPメソッドも追加してみました。

### (3) Access-Control-Allow-Methodsを追加

```apache:conf
<Location /api/company>
  <IfModule mod_headers.c>
     # アクセスを許可するURLを指定
     Header set Access-Control-Allow-Origin "http://localhost:4200"
     # 許可するリクエストヘッダの種類を指定
     Header set Access-Control-Allow-Headers "Content-Type"
     # 許可するメソッドを指定
     Header set Access-Control-Allow-Methods "GET"
  </IfModule>
</Location>
```

`POST`でアクセスすると、何らかのエラーが出る事を期待していたのですが、
正常にアクセスできてしまいました。設定は有効になっているようなのですが・・・

![a.jpg](https://qiita-image-store.s3.amazonaws.com/0/206289/a2b60639-41cf-2484-8adc-174aaf9c5046.jpeg)

そもそも、`Access-Control-Allow-Methods`を設定していなくてもアクセス出来ていたので、
POST、GET、プリフライトリクエストで使用される`OPTIONS`は常に許可されているのか、
どこか設定が間違っていたのか、分からず・・・

[許可する HTTP メソッドの種類を指定する](http://hakuhin.jp/js/resource_sharing.html#ACCESS_CONTROL_ALLOW_METHODS)
には、常に許可すると記載されているので、POSTやGETは指定しなくても許可されているのかな。

`PUT`でアクセスするようにツールを修正。

```js:company.service.ts
export class CompanyService <T> {
  constructor(private http: Http) {}
  getCompanyDetail() : Promise<T> {
    let sendUrl = 'http://*****/api/company/get_detail.html';
    let headers = new Headers({ 'Content-Type': 'application/json' });
    let options = new RequestOptions({ headers: headers });
    return this.http.put(url, {}, options).map(res => res.json()).toPromise();
  }
}
```

APIへアクセスすると、`PUT`は許可していないので、期待していたエラーがでました。

```
クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、http://*****/api/company/get_detail.html にあるリモートリソースの読み込みは拒否されます (理由: CORS ヘッダー ‘Access-Control-Allow-Methods’ にメソッドが見つからない)。
```

それにしても、Angular難しいです・・・。慣れるまで、まだまだ時間が掛かりそうです。