【axios+SAM+API Gateway】localhost에서 api를 두드리게 되기 위해서 고생한 이야기

소개



본고는 아래의 흐름에 따라, axios+SAM+API Gateway를 이용해, GET 및 POST 메소드의 API를 두드리는 곳까지를 목표로 하고 있습니다.
  • SAM을 사용하여 API 게이트웨이 및 lambda 구축
  • 로컬에서 방금 작성한 API Gateway의 GET 메소드를 두드린다
  • 로컬에서 방금 만든 API Gateway의 POST 메소드를 두드린다

  • 지난번 「로컬로부터 방금 작성한 API Gateway의 GET 메소드를 두드린다」라고 하는 것을 시도했습니다. 그 때, "도메인이 다른 경우는 CORS를 의식할 필요가 있다"는 것을 배웠습니다. 구체적으로는, API측에서 도메인간의 자원을 공유를 허가하는 것과 같은 내용을 header 에 포함해 반환할 필요가 있다, 라고 하는 것이었습니다.

    이번에는, 「로컬로부터 방금 작성한 API Gateway의 POST 메소드를 두드린다」라고 하는 것을 시도해 가고 싶습니다.

    3. 로컬에서 방금 만든 API Gateway의 POST 메서드를 두드리기



    그럼 POST 메소드를 두드릴 수 있도록 해 가고 싶습니다. 우선은 API측의 구현을 해 갑니다.POST의 API로 다음 lambda를 만듭니다.
    // post_item.py
    import json
    
    # import requests
    
    
    def lambda_handler(event, context):
        print(event)
        msg = json.loads(event['body'])['message']
        return {
            "statusCode": 200,
            'headers': {
                'Access-Control-Allow-Headers': 'Content-Type',
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
            },        
            "body": json.dumps({
                "message": "you posted this message: " + msg
            }),
        }
    

    또한 이 lambdaSAM 로 관리할 수 있도록 template.yaml 를 수정합니다.
    Resources:
      PostItemFunction:
        Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
        Properties:
          CodeUri: post_item/
          Handler: post_item.lambda_handler
          Runtime: python3.7
          Events:
            HelloWorld:
              Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
              Properties:
                Path: /post_items
                Method: post
    

    그리고 이것들을 빌드 배치해 API측의 준비는 완료입니다.
    이어서 앱 측을 수정합니다. 이번은 POST 메소드에 대응한 처리를 추가합니다.
    // App.js
    import React, {Component} from 'react';
    import axios from 'axios';
    
    class App extends Component {
        constructor(props){
            super(props);
            this.state = {
                'appMessage': 'hello,app',
                postApiMessage: 'before post'
            };
            this.doChange = this.doChange.bind(this);
            this.getApi = this.getApi.bind(this);
            this.postApiMessage = this.postApiMessage.bind(this);        
        }
    
        instance = axios.create({
            baseURL: 'https://flz1roclul.execute-api.ap-northeast-1.amazonaws.com/Prod'
        });
    
        doChange(e){
            this.setState({
                message: e.target.value
            })
        }
    
        getApi(){
            this.instance.get('/hello')
                .then((response) => {
                    this.setState({
                        'appMessage': response.data['message']
                    })
                })
                .catch(() => {
                    this.setState({
                        'appMessage': 'faild get message from button'
                    })                
                })
        }
    
        postApiMessage(){
            this.instance.post('/post_items', {'message': this.state.message})
              .then((results)=>{
                //console.log(results.json())
                console.log(results)
                this.setState({
                  'postApiMessage': 'postApiSuccess! ' + results.data['message']
                })
              })
              .catch((results) => {
                console.log(results)
                this.setState({
                  'postApiMessage': 'postApiFailed' + results
                })        
              })
          }
    
        render(){
            return(
                <div>
                    <p>{this.state.postApiMessage}</p>
                    <form>
                        <input type="text" value={this.state.message} onChange={this.doChange}/>
                        <input type="button" value="post api" onClick={this.postApiMessage} />
                    </form>
                    <div>{this.state.appMessage}</div>
                    <input type='button' onClick={this.getApi} value="button" />
                </div>
            )
        }
    }
    
    export default App;
    

    이번 처리는 앱 측에서 게시한 내용을 API 측에서 붙여 반환하는 단순한 것입니다.
    전회의 반성을 살려, 이번은 API측에 필요한 header를 미리 추가해 두었으므로, 괜찮다고 생각합니다!


    음...

    무엇이 속지 않았습니까?



    이번 오류 메시지는 다음과 같습니다.
    Access to XMLHttpRequest at 'https://{APIのURL}/Prod/post_items' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
    
    'Access-Control-Allow-Origin' 라고 생각했지만, 이번 중요한 것은 다음 장소였습니다.
    Response to preflight request doesn't pass access control check
    

    조사해 보면, 특정의 조건을 채우면(자), 실제의 메소드가 실행되기 전에 OPTIONS 메소드가 실행된다 (그러니까 preflight ) 같습니다. 그래서 이번에는 그것에 대응하는 메소드가 준비되어 있지 않았기 때문에, 에러가 되었다고.
    그래서 대책을 하려고 합니다. 메소드 부분은 필요한 header를 돌려주는 것만으로 좋기 때문에 이하와 같이 했습니다.
    // post_item_options.py
    import json
    
    # import requests
    
    
    def lambda_handler(event, context):
        return {
            "statusCode": 200,
            'headers': {
                'Access-Control-Allow-Headers': 'Content-Type',
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
            },        
            "body": json.dumps({
                "message": "this is the method to avoid cors for post method"
                # "location": ip.text.replace("\n", "")
            }),
        }
    

    그리고 이것을 template.yaml에 추가합니다.
    Resources:
      PostItemOptionsFunction:
        Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
        Properties:
          CodeUri: post_item_options/
          Handler: post_item_options.lambda_handler
          Runtime: python3.7
          Events:
            HelloWorld:
              Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
              Properties:
                Path: /post_items
                Method: options
    

    중요한 것은 POST 메소드와 Path는 같게 하는 것입니다. 이것은, preflight 메소드는 송신하려고 한 메소드에 대해서 날기 때문입니다.

    다시 시도



    API 측을 수정했으므로 다시 시도해 보았습니다.

    성공 post가 성공하고 메시지가 반환되었습니다!

    끝에



    안전한 API 환경을 구축하고 앱에서 GETPOST API를 두드릴 수 있습니다. 「단지 URL과 메소드를 지정해 두드리면 좋다」라고 생각하고 있었으므로, 설마 header 운운으로 이렇게 빠진다고는 생각하지 않았습니다. 만약 본고가 같은 고민에 직면하고 있는 사람의 도움이 되면 다행입니다.

    좋은 웹페이지 즐겨찾기