기술블로그

StatesAirline Server

이경찬 :) 2023. 2. 8. 14:14

CORS란?

 Cross Origin Resource Sharing의 약자로, 시스템 수준에서 타 도메인 간 자원 호출을 승인하거나 차단하는 것을 결정하는 것이다. 예를들어, 나 너네 서버에 POST 요청해도 되니? 라는 요청을 먼저 보내고 해도 된다는 응답이 오면 그때 POST 요청을 보내는 것이다.

그럼 CORS가 왜 필요한 것인가?

 다른 도메인에서의 자원을 호출하는 행위에 제한이 없으면 안전하지 않다. 그래서 서버가 허용한 클라이언트의 요청에만 응답하기 위해서 필요한 것이다.

서버가 CORS를 허용하기 위한 HTTP 헤더 각각의 목적을 이해해보자.

  • Access-Control-Allow-Origin : 단일 출처를 지정해서 브라우저가 해당 출처가 리소스에 접근하도록 허용한다. "*" 처리를 해주면 origin에 상관없이 모든 리소스에 접근하도록 허용해준다.
  • Access-Control-Allow-Methods : 리소스에 접근할 때 허용되는 메서드를 지정한다.
  • Access-Control-Allow-Headers : 사용자 정의 헤더를 받을 수 있음을 나타내준다. 예를 들어, " 'Access-Control-Allow-Headers' : 'X-PINGOTHER, Content-Type' " 이렇게 작성이 되어있다면, 'X-PINGOTHER, Content-Type' HTTP 헤더를 사용할 수 있게 된다.
  • Access-Control-Max-Age : prefilght request 요청 결과를 캐시할 수 있는 시간을 나타낸다. 다시 말해서 preflight request 유효기간을 설정해주는 것이다. 예를 들어 'Access-Control-Max-Age : 86400' 이라고 되어있으면, preflight request를 한번 보내고 허가를 받으면 86400초(24시간) 동안 preflight request를 굳이 보내지 않아도 리소스 요청을 할 수 있다.

Getting Started

1. statesairline

  • statesairline/app.js 는 서비스에 필요한 미들웨어와 웹 서버를 실행하는 코드가 작성되어 있습니다.

2. router

  • statesairline/router/ 안에는 airport API, book API, flight API 요청을 수행하는 라우터가 작성되어 있습니다. 작성된 라우터 내용을 통해 API 요청을 받을 수 있습니다.

3. controller

  • statesairline/controller/ 안에는 정의된 API 요청을 수행하는 코드를 작성해야 합니다. ( airportController.js 에 작성된 내용을 참고하여 작성합니다. )

4. repository

  • statesairline/repository/flightlist.js 는 서비스에서 제공하는 항공편 데이터가 작성되어 있습니다. 이 데이터로 서비스를 구현합니다.
  • statesairline/repository/airportlist.js 는 서비스에서 제공하는 공항 데이터가 작성되어 있습니다.

5.test

  • statesairline/__test__/statesairline.test.js 은 Jest 스펙을 가지고 있으며 코드 테스트를 위한 테스트 케이스가 작성되어 있습니다.
  • npm install 을 통해서 package.json에 설정된 패키지를 설치하세요.
  • npm start를 통해 서버를 실행하세요.
  • Postman으로 API 동작 여부를 확인하세요.
  • npm test를 통해 테스트를 실행하세요.
  • 서버에서 사용되는 포트 번호로 실행되지 않는 경우, 관리자 권한을 부여하여 실행하거나 해당 포트를 사용 중인 프로그램을 종료 후 재시작을 합니다.

Bare Minimum Requirements

  • statesairline/controller/flightController.js와 statesairline/controller/bookController.js 에 코드를 작성하세요.
  • Express 공식문서에서 req.query , req.params를 사용하는 방법을 확인하세요. Query와 Params를 기준으로 데이터를 필터링하는 코드를 작성해야 합니다.
  • 예약 데이터는 controller/bookController.js 안에 작성된 let booking = []; 배열에 저장해야 합니다.
  • Flight API -> 항공편 수정은 Advanced Challenges입니다.

Advanced Challenges

Advanced 콘텐츠의 문제를 해결하려면, 충분한 컨텍스트가 필요할 수 있습니다. Bare minimum requirements 를 완료했다면 도전하세요.

생성한 데이터를 수정할 수 없다면, 매번 데이터를 지우고 다시 생성해야 합니다. 이런 불편을 서비스에서 제거하려면, 데이터를 수정할 수 있는 기능을 추가하면 됩니다.
Advanced 콘텐츠에서는 PUT 요청에 따라 항공편을 수정하는 기능을 구현합니다.

1. 목표

  • Flight API -> 항공편 수정에서 정의한 API 요청을 수행하는 코드를 작성하세요.
  • Advanced Challenges에 있는 모든 테스트를 통과해야 합니다.

2. 참고 내용

  • flight.uuid는 변경되면 안 됩니다.
  • flight JSON의 일부만 수정할 수 있어야 합니다.

Sprint 복기

States Airline 서버를 구현하는 과제이다. express프레임워크를 이용해서 만든 서버이고, 클라이언트 요청에 따라 항공편과 예약 데이터를 조회, 생성, 수정, 삭제하는 기능을 수행할 수 있어야 한다.

 

app.js

express 프레임워크를 사용해서 서버를 구현하였고 app.use를 통해 각각  flightRouter, bookRouter, airportRouter에 분기점을 만들어 주었다.

const express = require('express');
const cors = require('cors');
const app = express();

// 모든 서버는 요청을 받을수 있는 포트 번호를 필요로 합니다.

// HTTP server의 표준 포트는 보통 80 번 이지만, 보통 다른 서버에서 사용중이기 때문에 접근할 수 없습니다.
// 따라서 우리는 보통 테스트 서버 포트로 3000, 8080, 1337 등을 활용합니다.

// PORT는 아파트의 호수와도 같습니다. 서버로 요청을 받기 위해서는 다음과 같이 포트 번호를 설정 합니다.
// (* 때에 따라 다른 포트번호를 열고 싶다면, 환경 변수를 활용 하기도 합니다.)
const port = 3001;

const flightRouter = require('./router/flightRouter');
const bookRouter = require('./router/bookRouter');
const airportRouter = require('./router/airportRouter');

app.use(cors());
app.use(express.json());

app.use('/flight', flightRouter);
app.use('/book', bookRouter);
app.use('/airport', airportRouter);

app.get('/', (req, res) => {
  res.status(200).send('Welcome, States Airline!');
});

app.use((req, res, next) => {
  res.status(404).send('Not Found!');
});

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send({
    message: 'Internal Server Error',
    stacktrace: err.toString()
  });
});

app.listen(port, () => {
  console.log(`[RUN] StatesAirline Server... | http://localhost:${port}`);
});

module.exports = app;

 

Router.js

- 각 Router.js 파일에서는 Countroller에서 작성된 함수들을 가져와서 각 요청에 맞는 함수들을 시켜주고 있다.

 

결국 Controller 함수들이 Router.js에 불려와서 실행이되고, 이 라우터 들이 app.js에 불려와서 사용된다는 것을 알 수 있다. 그래서 app.js에 있는 
app.use('/flight', flightRouter); 는
app.get('/flight', findAll), app.get('/flight/:id', findById), app.put('/flight/:id', update)을 포함하고 있다고 볼수 있고,
app.use('/book', bookRouter); 는
app.get('/book', findById), app.post('/book', create), app.delete('/book', deleteById)을 포함하고 있다고 볼수 있고,
app.use('/airport', airportRouter); 는
app.get('/airport', findAll)을 포함하고 있다고 볼수 있다.

 

flightController.js

const flights = require('../repository/flightList');
const fs = require('fs');

module.exports = {
  // [GET] /flight
  // 요청 된 파라미터 departure_times, arrival_times 값과 동일한 값을 가진 항공편 데이터를 조회합니다.
  // 요청 된 파라미터 departure, destination 값과 동일한 값을 가진 항공편 데이터를 조회합니다.
  findAll: async (req, res) => {
    const { departure_times, arrival_times, destination, departure } = req.query;
    // TODO:
    if(req.query.departure_times && req.query.arrival_times){
      let filtering1 = flights.filter( el => el.departure_times === req.query.departure_times && el.arrival_times === req.query.arrival_times)
      return res.status(200).json(filtering1);
    }
    if(req.query.departure && req.query.destination){
      let filtering2 = flights.filter(el => el.departure === req.query.departure && el.destination === req.query.destination)
      return res.status(200).json(filtering2);
    }

    return res.json(flights);
  },
  // [GET] /flight/:uuid
  // 요청 된 uuid 값과 동일한 uuid 값을 가진 항공편 데이터를 조회합니다.
  findById: async (req, res) => {
    const { uuid } = req.params;
    // TODO:
    if(req.params.id) {
      let filter = flights.filter(el => el.uuid === req.params.id)
      return res.status(200).json(filter);
    }
    return res.json(flights);

  },

  // Advanced
  // [PUT] /flight/:uuid 요청을 수행합니다.
  // 요청 된 uuid 값과 동일한 uuid 값을 가진 항공편 데이터를 요쳥 된 Body 데이터로 수정합니다.
  update: (req, res) => {
    const { uuid } = req.params;
    const bodyData = req.body;
     // TODO:
    if (uuid) {
      const list = flights.filter((item) => {
        return item.uuid === uuid;
      })

      let obj = Object.assign(list[0], bodyData);
      return res.json(obj);
    }
  }
};

bookController.js

// POST /book에서 사용할 uuid입니다.
const flights = require('../repository/flightList');
const { v4: uuid } = require('uuid');
// 항공편 예약 데이터를 저장합니다.
let booking = [];

module.exports = {
  // [GET] /book 요청을 수행합니다.
  // 전체 예약 데이터를 조회합니다.
  findAll: (req, res) => {
    return res.status(200).json(booking);
  },
  // [GET] /book/:phone 요청을 수행합니다.
  // 요청 된 phone과 동일한 phone 예약 데이터를 조회합니다.
  findByPhone: (req, res) => {
    const {phone} = req.params;
    let list = booking;
    if(phone) {
      list = list.filter((item) => {
        return phone === item.phone;
      })
     return res.status(200).json(list);
    }
    return res.status(200).json(list)

  },
  // [GET] /book/:phone/:flight_uuid 요청을 수행합니다.
  // 요청 된 id, phone과 동일한 uuid, phone 예약 데이터를 조회합니다.
  findByPhoneAndFlightId: (req,res) => {
    const {phone, flight_uuid} = req.params;
    // TODO:
    let list = booking;
    if(flight_uuid){
      list = list.filter((item) => {
        return flight_uuid === item.flight_uuid;
      })
      res.status(200).json(list);
    }
    if(phone){
      list = list.filter((item) => {
        return phone=== item.phone;
      })
      res.status(200).json(list);
    }
    return res.status(200).json(list)
  },
  // [POST] /book 요청을 수행합니다.
  // 요청 된 예약 데이터를 저장합니다.
  create:  (req, res) => {
    // POST /book에서 사용할 booking_uuid입니다.
    const booking_uuid = uuid();
    // TODO:
    booking.push(req.body)
    return res.status(201).json({});
  },

  // Optional
  // [DELETE] /book/:booking_uuid 요청을 수행합니다.
  // 요청 된 id, phone 값과 동일한 예약 데이터를 삭제합니다.
  deleteByBookingId:  (req, res) => {
    const {booking_uuid} = req.params;
    // TODO:
    if(req.query.phone && req.query.flight_uuid) {
      booking = booking.filter(el => el.phone !== req.query.phone && el.flight_uuid !== req.query.flight_uuid)
      return res.status(200).filter(booking);
    }
  }
};

 

 

추가.

app.use

"app.use"는 Node.js의 인기 있는 웹 애플리케이션 프레임워크인 Express.js의 메서드이다. "app.use" 메서드는 Express.js 응용 프로그램에 미들웨어 기능을 추가하는 데 사용됩니다.

미들웨어 기능은 요청 및 응답 개체에 액세스할 수 있는 기능으로, 요청 데이터 구문 분석, 응답 헤더 추가 또는 오류 트리거와 같은 작업을 수행할 수 있습니다. 미들웨어 기능은 응용 프로그램에 추가된 순서대로 실행되므로 "app.use"를 사용하여 특정 순서로 실행되는 여러 미들웨어 기능을 추가할 수 있습니다.

 

const express = require("express");
const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use((req, res, next) => {
  console.log("Request received!");
  next();
});

app.use("/", (req, res) => {
  res.send("Hello, World!");
});

app.listen(3000, () => {
  console.log("App listening on port 3000!");
});

위의 예에서 "app.use"는 express.json()과 express.urlencoded()라는 두 가지 미들웨어 기능을 추가하는 데 사용됩니다. 이러한 미들웨어 기능을 통해 애플리케이션은 각각 JSON 및 URL 인코딩 요청 데이터를 처리할 수 있습니다. 두 번째 app.use 메서드는 요청이 수신되면 콘솔에 메시지를 기록하고 마지막 app.use 메서드는 루트 경로에 대한 "GET" 요청을 처리합니다.