CHAPTER 14. Send and receive data with Http
Intro
๋๋ผ์ด ์ผ์ ์๋์ง๋ง ๋ง์ ์ ๋ฌด๋ ๋ฐฑ์๋ ์๋ฒ์ ์น ์์ฉ ํ๋ก๊ทธ๋จ์ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด๊ณ ๋ฐ์ดํฐ๋ฅผ ๋ค์ ๋ณด๋ด๋๋ก ์์ฒญํ๋ค. ์ผ๋ฐ์ ์ผ๋ก WebSocket๊ณผ ๊ฐ์ ๋ค๋ฅธ ๋์์ด ์๋๋ผ๋ HTTP๋ฅผ ํตํด ์ํ๋๋ค. Angular 2๋ http ๋ชจ๋์ ์ ๊ณตํ์ง๋ง ๊ฐ์ ๋ก ์ฌ์ฉํ์ง๋ ์๋๋ค. ์ํ๋ ๊ฒฝ์ฐ ์ ํธํ๋ HTTP ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋น๋๊ธฐ ์์ฒญ์ ๋ณด๋ผ ์ ์๋ค.
์ ์ ํ์ ์ค ํ๋๋ ํ์ฌ polyfill()๋ก ์ฌ์ฉํ ์ ์์ง๋ง ๋ธ๋ผ์ฐ์ ์์ ํ์ค์ด ๋์ด์ผํ๋ fetchAPI์ด๋ค. ๊ฐ์ ธ ์ค๊ธฐ ๋๋ ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ์ฑ์ ์๋ฒฝํ๊ฒ ๋ง๋ค ์ ์๋ค. ์ฌ์ค ๊ทธ๊ฒ์ Http ๋ถ๋ถ์ด Angular2์์ ์ํ๋๊ธฐ ์ ์ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค. ํ๋ ์ ์ํฌ๊ฐ ๋ฐ์ดํฐ๋ฅผ ์์ ํ์์ ์ธ์ํ๊ณ ๋ณ๊ฒฝ ๊ฐ์ง๋ฅผ ์คํํด์ผํ๋ค๋ ํน๋ณํ ํธ์ถ์ด ํ์์์ด ํ๋ฅญํ๊ฒ ์๋ํ๋ค. (AngularJS์๋ ๋ฌ๋ฆฌ 1.x, ์ฌ๊ธฐ์ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด $ scope.apply ()๋ฅผ ํธ์ถํด์ผ ํ๋ค : ์ด๊ฒ์ Angular 2์ ๊ทธ ์์ญ์ ๋ง์ ์ด๋ค!)
๊ทธ๋ฌ๋ ํ๋ ์ ์ํฌ์ ์ต์ํ๋ค๋ฉด ํต์ฌ ํ์์ ์ ๊ณตํ๋ HttpModule์ด๋ผ๋ ์์ ๋ชจ๋์ ์ฌ์ฉํ ๊ฒ์ด๋ค. ๊ทธ๊ฒ์ ๋ ๋ฆฝ์ ์ธ ๋ชจ๋์ด๋ค. ๊ทธ๋์ ์ ๋ง๋ก, ๋น์ ์ด ์ํ๋ ๋๋ก ํ์์ค. Fetch API ์ ์์ ์ถฉ๋ถํ ๋ฐ์ ํ๊ฒ ๋ฐ์ํ๋ค.
๊ทธ๊ฒ์ ์ฌ์ฉํ๋ ค๋ฉด @ angular / httppackage์ ํด๋์ค๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค. ์ ์ด ๋ชจ๋์ fetch๋ณด๋ค ๋ ์ ํธ ํ๋๊ฐ? ๋๋ต์ ๊ฐ๋จํฉ๋๋ค. ์์ผ๋ก ์ดํด ๋ณด๊ฒ ์ง๋ง Http ๋ชจ๋์ ์ฌ์ฉํ๋ฉด ๋ฐฑ์๋ ์๋ฒ๋ฅผ ๋ชจ๋ฐฉํ๊ณ ๊ฐ์ง ์๋ต์ ๋ฐํ ํ ์ ์๋ค. ์ ๋ง, ์ ๋ง ์ ์ฉํ๋ค!! API๋ก ๋ค์ด๊ฐ๊ธฐ ์ ์ ๋ง์ง๋ง์ผ๋ก, Http ๋ชจ๋์ ๋ฐ์ ํ ํ๋ก๊ทธ๋๋ฐ ํจ๋ฌ๋ค์์ ๋ง์ด ์ฌ์ฉํ๋ค. ๋ฐ๋ผ์ Reactive Programming ์ฅ์ ๊ฑด๋ ๋ฐ๋ฉด ์ง๊ธ ๋์๊ฐ์ ์ฝ์ ์ ์๋ ์ข์ ์๊ฐ์ด ๋ ๊ฒ์ด๋ค.)
Getting data
Http ๋ชจ๋์ Http๋ผ๋ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ค. ์ด ์๋น์ค๋ ๋ชจ๋ ์์ฑ์์ ์ฝ์ ํ ์ ์๋ค. ์๋น์ค๊ฐ ๋ค๋ฅธ ๋ชจ๋์์ ์ค๋ ๊ฒ์ด๋ฏ๋ก ๊ตฌ์ฑ ์์ ๋ ์๋น์ค์์ ์๋์ผ๋ก ์๋น์ค๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ํด์ผํ๋ค. ์ด๋ ๊ฒ ํ๋ ค๋ฉด HttpModule์ ๋ฃจํธ ๋ชจ๋๋ก ๊ฐ์ ธ์จ๋ค.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
@NgModule({
imports: [BrowserModule, HttpModule],
declarations: [PonyRacerAppComponent],
bootstrap: [PonyRacerAppComponent]
})
export class AppModule {
}
์ด ์์ ์ด ์๋ฃ๋๋ฉด Httpservice๋ฅผ ํ์ํ ๊ณณ ์ด๋ ์๋ ์ฝ์ ํ ์ ์๋ค.
@Component({
selector: 'ponyracer-app',
template: `<h1>PonyRacer</h1>`
})
export class PonyRacerAppComponent {
constructor(private http: Http) {
}
}
๊ธฐ๋ณธ์ ์ผ๋ก Http ์๋น์ค๋ XMLHttpRequest๋ฅผ ์ฌ์ฉํ์ฌ AJAX ์์ฒญ์ ์ฒ๋ฆฌํ๋ค. ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ HTTP ๋์ฌ์ ์ผ์นํ๋ ์ฌ๋ฌ ๊ฐ์ง ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ค.
โข get โข post โข put โข delete โข patch โข head
AngularJS 1.x์์ $ http ์๋น์ค๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ์ฝ์์ ํฌ๊ฒ ์์กดํ๊ณ ์์์ ๊ธฐ์ตํ ๊ฒ์ด๋ค. ๊ทธ๋ฌ๋ Angular 2์์๋ ์ด๋ฌํ ๋ชจ๋ ๋ฉ์๋๊ฐ Observable ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค.
Observables๋ฅผ Http๋ฅผ ์ฌ์ฉํ์ฌ ์์ฒญ์ ์ทจ์ํ๊ณ , ๋ค์ ์๋ํ๊ณ , ์ฝ๊ฒ ์์ฑํ๋ ๋ฑ์ ๋ช ๊ฐ์ง ์ด์ ์ด ์๋ค.
PonyRacer์ ๋ฑ๋ก ๋ ๋ ์ด์ค๋ฅผ ๊ฐ์ ธ์ ๋ณด์. ๋ฐฑ์๋๊ฐ ์ด๋ฏธ ์คํ๋์ด RESTful API๋ฅผ ์ ๊ณตํ๋ค๊ณ ๊ฐ์ ํ์. ๊ฒฝ์ฃผ๋ฅผ ๊ฐ์ ธ ์ค๋ ค๋ฉด 'http : //backend.url/api/races'์ ๊ฐ์ URL์ GET ์์ฒญ์ ๋ณด๋ด์ผ ํ๋ค.
์ผ๋ฐ์ ์ผ๋ก HTTP ํธ์ถ์ ๊ธฐ๋ณธ URL์ ํ๊ฒฝ์ ๋ฐ๋ผ ์ฝ๊ฒ ๊ตฌ์ฑ ํ ์์๋ ๋ณ์ ๋๋ ์๋น์ค์ ์ ์ฅ๋๋ค. ๋๋ REST API๊ฐ Angular ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ๋์ผํ ์๋ฒ์์ ์ ๊ณต๋๋ ๊ฒฝ์ฐ ์๋ URL ์ธ '/ api / races'๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
์ด๋ฌํ ์์ฒญ์ Http ์๋น์ค๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ๋จํ๋ค.
http.get(`${baseUrl}/api/races`)
์ด๊ฒ์ Observable์ ๋ฐํํ๋ฉฐ, Observable์ ์ฌ์ฉํ์ฌ ์๋ต ์์ ์ ์ ์ฒญํ ์ ์๋ค. ์๋ต์ ๋ช ๊ฐ์ง ํ๋์ ํธ๋ฆฌํ ๋ฉ์๋๊ฐ ์๋ Response ๊ฐ์ฒด์ด๋ค. ์ํ ์ฝ๋, ํค๋ ๋ฑ์ ์ฝ๊ฒ ์ก์ธ์ค ํ ์ ์๋ค.
http.get(`${baseUrl}/api/races`)
.subscribe(response => {
console.log(response.status); // logs 200
console.log(response.headers); // logs []
});
๋ฌผ๋ก ์๋ต ๋ณธ๋ฌธ์ด ๊ฐ์ฅ ํฅ๋ฏธ๋ก์ด ๋ถ๋ถ์ด๋ค. ๊ทธ๋ฌ๋ ์ก์ธ์คํ๋ ค๋ฉด ๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค. โข text () ์ผ๋ถ ํ ์คํธ๊ฐ ํ์ํ ๊ฒฝ์ฐ ๋ถ์์ด ์ํ๋๋ค. โข json () JSON ๊ฐ์ฒด๋ฅผ ์์ํ๋ฉด ๊ตฌ๋ฌธ ๋ถ์์ด ์ํ๋๋ค.
http.get(`${baseUrl}/api/races`)
.subscribe(response => {
console.log(response.json());
// logs the array of races
});
๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด๋ ๊ฒ๋ ๋งค์ฐ ์ฝ๋ค. ๊ฒ์ ํ URL๊ณผ ๊ฐ์ฒด๊ฐ์๋ post() ๋ฉ์๋๋ฅผ ํธ์ถํ๊ธฐ ๋งํ๋ฉด ๋๋ค.
// you currently need to stringify the object you send
http.post(`${baseUrl}/api/races`, newRace)
Transforming data
Observable ๊ฐ์ฒด๋ฅผ ์๋ต์ผ๋ก ๋ฐ๊ณ ์์ผ๋ฏ๋ก ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ฒ ๋ณํ ํ ์ ์๋ค๋ ์ฌ์ค์ ์์ง ๋ง์์ค. ๊ฒฝ์ฃผ ์ด๋ฆ์ ๋ชฉ๋ก์ ์ํ๋๊ฐ? ์ธ์ข ์ ์์ฒญํ๊ณ ์ด๋ฆ์ ๋งคํํ๋ฉด ๋๋ค! ์ด๋ ๊ฒ ํ๋ ค๋ฉด RxJS์ ๋ํ mapoperator๋ฅผ ๊ฐ์ ธ์์ผ ํ๋ค. Angular ํ์ ์ฑ์ ์ ์ฒด RxJS ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํฌํจํ๊ธฐ๋ฅผ ์ํ์ง ์์ผ๋ฏ๋ก ๋ช ์ ์ ์ผ๋ก ํ์ํ ์ฐ์ฐ์๋ฅผ ๊ฐ์ ธ์์ผ ํ๋ค.
import 'rxjs/add/operator/map';
์ด์ ์ฌ์ฉํ ์ ์๋ค.
http.get(`${baseUrl}/api/races`)
// extract json body
.map(res => res.json())
.subscribe(races => {
// store the array of the races in the component
this.races = races;
});
์ด๋ฌํ ์ข ๋ฅ์ ์์ ์ ์ผ๋ฐ์ ์ผ๋ก ์ ์ฉ ์๋น์ค๋ก ์ํ๋๋ค. RaceService์ ๊ฐ์ ์๋น์ค๋ฅผ ๋ง๋๋ ๊ฒฝํฅ์ด ์๋ค. RaceService๋ ๋ชจ๋ ์์ ์ด ์๋ฃ๋ ๊ณณ์ด๋ค. ๊ทธ๋ฐ ๋ค์ ๋ด ๊ตฌ์ฑ ์์๋ ์๋น์ค ๋ฐฉ๋ฒ์ ๊ตฌ๋ ํด์ผํ๋ฉฐ ํ๋ ๋ด๋ถ์์ ๋ฌด์จ ์ผ์ด ์ผ์ด๋๊ณ ์๋์ง ์์ง ๋ชปํ๋ค.
raceService.list()
.subscribe(races => {
// store the array of the races in the component
this.races = races;
});
๋ํ RxJS๋ฅผ ์ฌ์ฉํ์ฌ ์คํจํ ์์ฒญ์ ๋ช ๋ฒ ๋ค์ ์๋ ํ ์ ์๋ค.
raceService.list()
// if the request fails, retry 3 times
.retry(3)
.subscribe(races => {
// store the array of the races in the component
this.races = races;
});
Advanced options
๋ฌผ๋ก ๋ณด๋ค ์ธ๋ฐํ๊ฒ ์์ฒญ์ ์กฐ์ ํ ์ ์๋ค. ๋ชจ๋ ๋ฉ์๋๋ RequestOption ๊ฐ์ฒด๋ฅผ ์ ํ์ ๋งค๊ฐ ๋ณ์๋ก ์ฌ์ฉํ์ฌ ์์ฒญ์ ๊ตฌ์ฑ ํ ์ ์๋ค. ๋ช ๊ฐ์ง ์ต์ ์ด ์ ๋ง ์ ์ฉํ๋ฉฐ ์์ฒญ์ ๋ชจ๋ ๊ฒ์ ๋ฌด์ํ ์ ์๋ค. ์ด ์ต์ ์ค ์ผ๋ถ๋ Fetch API์์ ์ ๊ณตํ๋ ๊ฒ๊ณผ ๋์ผํ ๊ฐ์ ๊ฐ๋๋ค. url ์ต์ ์ ๋งค์ฐ ๋ช ํํ๋ฉฐ ์์ฒญ์ URL์ ๋ฎ๋๋ค. method ์ต์ ์ Request Method.Get๊ณผ ๊ฐ์ด ์ฌ์ฉํ HTTP ๋์ฌ์ด๋ค. ์๋์ผ๋ก ์์ฒญ์ ์์ฑํ๋ ค๋ฉด ๋ค์์ ์์ฑํ ์ ์๋ค.
const options = new RequestOptions({ method: RequestMethod.Get });
http.request(`${baseUrl}/api/races/3`, options)
.subscribe(response => {
// will get the race with id 3
});
search๋ URL์ ์ถ๊ฐ ํ URL ๋งค๊ฐ ๋ณ์๋ฅผ ๋ํ๋ธ๋ค. URLSearchParams ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ ์ง์ ํ๋ฉด ์ ์ฒด URL์ด ์์ฑ๋๋ค.
const searchParams = new URLSearchParams();
searchParams.set('sort', 'ascending');
const options = new RequestOptions({ search: searchParams });
http.get(`${baseUrl}/api/races`, options)
.subscribe(response => {
// will return the races sorted
this.races = response.json();
});
ํค๋ ์ต์ ์ ์์ฒญ์ ๋ช ๊ฐ์ง ๋ง์ถค ํค๋๋ฅผ ์ถ๊ฐํ๋ ๊ฒฝ์ฐ์ ์ ์ฉํ๋ค. ์๋ฅผ ๋ค์ด, JSON Web Token๊ณผ ๊ฐ์ ์ผ๋ถ ์ธ์ฆ ๊ธฐ์ ์ ํ์ํ๋ค.
const headers = new Headers();
headers.append('Authorization', `Bearer ${token}`);
http.get(`${baseUrl}/api/races`, new RequestOptions({ headers }))
.subscribe(response => {
// will return the races visible for the authenticated user
this.races = response.json();
});
Jsonp
์น ๋ธ๋ผ์ฐ์ ์์ ์ํ๋๋ ๋์ผ ์์ ์ ์ฑ ์ ์ํด ์ฐจ๋จ๋์ง ์๊ณ API์ ์ก์ธ์ค ํ ์ ์๋๋ก CORS๋ฅผ ์ฌ์ฉํ์ง ์๊ณ JSONP (Padding with JSON)๋ฅผ ์ฌ์ฉํ๋ ์น ์๋น์ค๊ฐ ์๋ค. ์๋ฒ๊ฐ JSON ๋ฐ์ดํฐ๋ฅผ ์ง์ ๋ฐํํ์ง๋ ์์ง๋ง ์ฝ๋ฐฑ์ผ๋ก ์ ๋ฌ ๋ ํจ์๋ก ๋ํ ํ๋ค.
์๋ต์ ์คํฌ๋ฆฝํธ๋ก ๋์์ค๊ณ ์คํฌ๋ฆฝํธ๋ ๋์ผํ ์ถ์ฒ ์ ์ฑ ์ ์ ์ฉ์๋ฐ์ง ์์ต๋๋ค. ์ผ๋จ๋ก๋๋๋ฉด ์๋ต์ ํฌํจ ๋ JSON ๊ฐ์ ์ก์ธ์ค ํ ์ ์์ต๋๋ค.
HttpModule ์ธ์๋ JsonpService๋ฅผ ์ ๊ณตํ๋ JsonpModule์ด ์๋ค. JsonpModule์ ์ด๋ฌํ API์ ์ฝ๊ฒ ์ํธ ์์ฉํ ์์๊ฒ ํด์ฃผ๋ฉฐ, ์ฐ๋ฆฌ ๋ชจ๋์๊ฒ ํ์ํ ์ผ์ ํ๋ค. ํธ์ถ ํ ์๋น์ค์ URL์ ์ง์ ํ๊ณ JSONP_CALLBACK์ ์ฝ๋ฐฑ ๋งค๊ฐ ๋ณ์ ๊ฐ์ผ๋ก ์ถ๊ฐํ๊ธฐ๋ง ํ๋ฉด ๋๋ค.
๋ค์ ์์ ์์๋ JSONP๋ฅผ ์ฌ์ฉํ์ฌ Github ์กฐ์ง์์ ๋ชจ๋ ๊ณต๊ฐ Repos๋ฅผ ๊ฐ์ ธ์จ๋ค.
jsonp.get('https://api.github.com/orgs/Ninja-Squad/repos?callback=JSONP_CALLBACK')
// extract json
.map(res => res.json())
// extract data
.map(res => res.data)
.subscribe(response => {
// will return the public repos of Ninja-Squad
this.repos = response;
});
Tests
์ด์ ์ฐ๋ฆฌ๋ ์ข ์กฑ์ ๊ฐ์ ธ ์ค๊ธฐ ์ํด HTTP ์ข ์ ์ ํธ์ถํ๋ ์๋น์ค๋ฅผ ๊ฐ๊ฒ ๋๋ค. ์ด๋ป๊ฒ ํ ์คํธ ํ๋๊ฐ?
@Injectable()
export class RaceService {
constructor(private http: Http) {
}
list() {
return this.http.get('/api/races').map(res => res.json());
}
}
๋จ์ ํ ์คํธ์์๋ HTTP ์๋ฒ๋ฅผ ์ค์ ๋ก ํธ์ถํ๊ณ ์ถ์ง ์๊ณ ๊ฐ์ง ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๊ธฐ ์ํด HTTP ํธ์ถ์ "๊ฐ์ง"๋ก ๋ง๋ค๊ณ ์ถ๋ค. ์ด๋ฅผ ์ํด MockBackend๋ผ๋ ํ๋ ์ ์ํฌ๊ฐ ์ ๊ณตํ๋ ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ง ๊ตฌํ์ผ๋ก Http ์๋น์ค์ ๋ํ ์์กด์ฑ์ ๋์ฒด ํ ์ ์๋ค.
import { async, TestBed } from '@angular/core/testing';
import { BaseRequestOptions, Response, ResponseOptions, RequestMethod } from '@angular/http';
import { MockBackend, MockConnection } from '@angular/http/testing';
import 'rxjs/add/operator/map';
describe('RaceService', () => {
let raceService;
let mockBackend;
beforeEach(() => TestBed.configureTestingModule({
providers: [
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (backend, defaultOptions) => new Http(backend, defaultOptions),
deps: [MockBackend, BaseRequestOptions]
},
RaceService
]
}));
beforeEach(() => {
raceService = TestBed.get(RaceService);
mockBackend = TestBed.get(MockBackend);
});
it('should return an Observable of 2 races', async(() => {
// fake response
const hardcodedRaces = [new Race('London'), new Race('Lyon')];
const response = new Response(new ResponseOptions({ body: hardcodedRaces }));
// on a the connection
mockBackend.connections.subscribe((connection: MockConnection) => {
// return the fake response when we receive a request
connection.mockRespond(response);
});
// call the service
raceService.list().subscribe(races => {
// check that the returned array is deserialized as expected
expect(races.length).toBe(2);
});
}));
});
๋ํ ๊ธฐ๋ณธ HTTP ์์ฒญ์ ๋ช ๊ฐ์ง ๋จ์ ๋ฌธ์ ์ถ๊ฐ ํ ์ ์๋ค.
mockBackend.connections.subscribe((connection: MockConnection) => {
// return the fake response when we receive a request
connection.mockRespond(response);
// check that the underlying HTTP request was correct
expect(connection.request.method).toBe(RequestMethod.Get);
expect(connection.request.url).toBe('/api/races');
});
Reference URL
Last updated
Was this helpful?