미들웨어를 사용하여 RESTful 응답 차단

HTTP 서버를 구축할 때, 우리는 보통 클라이언트와 서버가 데이터를 왔다 갔다 전달하여 자원을 만들고, 갱신하고, 읽고, 삭제하는 황금 예로 넘어간다. (즉, RESTful services)이것은 텍스트 파일, 이진 데이터 (예:.mov,.mp4 파일), 심지어는 미리 포맷된 데이터 (JSON, XML 등) 일 수도 있으며, 다른 프로그램이 장래에 사용할 수 있도록 제공될 수도 있다.여러 클라이언트가 한 번에 한 대의 서버와 통신할 수 있고 서버는 시종일관 일치된 데이터를 제공하며 어떠한 간섭도 하지 않는다.
이 황금 예는 우리가 클라이언트와 서버 간의 통신 방식을 간단히 이야기하는 데 목적을 두고 있다.더 복잡한 해결 방안은 대다수 사람들이 생각하는 것보다 잦지만 대화에서 제외된다.본고에서 저는 특히 제3자 API의 내부 사용과 그들이 제공한 기능을 어떻게 활용하고 고객이 받은 응답에 대한 통제를 유지하는지에 주목합니다.
내부 서버가 클라이언트와 제3자 API 간의 통신의 중개인이 아니라고 가정하면 클라이언트가 받은 응답에 대해 어떤 품질 제어를 해야 한다.표준 Go net/http 패키지는 수신 요청이 예상 목표( Handler )에 도달하기 전에 차단하는 기능을 제공합니다.그러나 응답이 클라이언트에 도착하기 전에 미리 준비한 방법이 없습니다.

카탈로그

  • Receiving Requests from Browser Clients
  • Receiving Requests from Programmatic Clients
  • Using third-party APIs
  • Intercepting Requests
  • Intercepting Responses
  • Credits
  • 브라우저 클라이언트로부터 요청 수신


    시간을 절약하기 위해 가장 자주 사용하는 요청 방법인'GET'에 대해 이야기해 봅시다.GET 요청은tin의 설명에 따라 실행되며 메모리에서 일부 데이터(예를 들어 DEV에서 온 댓글,GitHub에서 온 프로필, 트위터에서 온 요약 등)를 검색합니다.브라우저에 URL(예: https://foresthoffman.com)을 입력하면 기본적으로 브라우저에서 URL을 가져옵니다.이것이 바로 우리가 인터넷에서 읽는 방식이다.
    서버의 업무는 클라이언트의 요구를 만족시키는 것이다.따라서 브라우저가 https://foresthoffman.com에 GET 요청을 하면 서버로 라우팅되어 응답을 기다리는 요청이 생성됩니다.내 예에서, 내 사이트는 HTML 파일로 응답을 하는데, 이 파일은 다른 외부 파일이 필요합니다. 이로 인해 브라우저가 사이트를 완전히 불러올 때까지 더 많은 GET 요청을 할 수 있습니다.
    GET 요청(즉 코드와 코드 대화)을 프로그래밍 방식으로 받는 서버의 경우 일이 더욱 흥미로워진다.

    프로그래밍 클라이언트로부터 요청을 받다


    만약에 GET 요청이 정적 노드에 도달하기를 원하는 서버가 있다면 프로그래밍 클라이언트는 이 노드에서 요청을 추출하고 필요에 따라 우리의 응답을 해석할 수 있습니다.다음은 /example 엔드포인트를 호스팅하는 로컬 호스트 서버의 예입니다.
    package main
    
    import "net/http"
    
    func exampleHandler() http.Handler {
       return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
          w.WriteHeader(http.StatusOK)
          _, err := w.Write([]byte("hey"))
          if err != nil {
             panic(err)
          }
       })
    }
    
    func main() {
       http.Handle("/example", exampleHandler())
       err := http.ListenAndServe(":9001", nil)
       if err != nil {
          panic(err)
       }
    }
    
    그런 다음 클라이언트는 볼륨을 사용하여 끝점을 얻을 수 있습니다.
    curl -X "GET" http://localhost:9001/example
    
    응답 본문은 hey으로 구성되어야 합니다.이 자체가 복잡하지는 않은 것 같지만 응답이 이 서버 응답 구조에 근거한 JSON 형식이라면 상상해 보세요.
    type Response struct {
       Message string `json:"message"`
    }
    
    프로세서를 적절하게 변경해야 합니다.
    func exampleHandler() http.Handler {
       return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
          respBytes, err := json.Marshal(Response{Message: "hey"})
          if err != nil {
             panic(err)
          }
          w.WriteHeader(http.StatusOK)
          _, err = w.Write(respBytes)
          if err != nil {
             panic(err)
          }
       })
    }
    
    그리고curl 출력은:{"message":"hey"}이어야 합니다.클라이언트가 이 JSON을 분석할 수 있다고 가정하면 클라이언트에서 거울 대상 구조를 만들 수 있습니다.쿨!

    타사 API 사용


    타사 API 지원에 의존하는 일부 작업의 경우 Google Cloud, MongoDB, Tus.io 등 서버 자체의 복잡성이 가중됩니다. 타사 API가 제공하는 응답을 제어할 수 없기 때문입니다.이제 제3자 처리 프로그램을 추가합니다.
    func thirdPartyHandler() http.Handler {
       return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
          respBytes, err := json.Marshal(struct{
             Timestamp string `json:"timestamp"`
          }{
             Timestamp: fmt.Sprint(time.Now().UnixNano()),
          })
          if err != nil {
             panic(err)
          }
          w.WriteHeader(http.StatusOK)
          _, err = w.Write(respBytes)
          if err != nil {
             panic(err)
          }
       })
    }
    
    이 제3자 처리 프로그램은 요청을 받아 현재 날짜를 가져오고 1970년 1월 1일 이후의 나초 문자열로 포맷합니다.비록 이 시간 스탬프는 매우 편리하지만, 그것은 우리가 자신의 처리 프로그램을 위해 실현한 구조에 부합되지 않는다.main에서 타사 처리 프로그램에 자신의 단점을 제공한 후에 우리는 응답 구조의 차이를 볼 수 있다.
    http.Handle("/third", thirdPartyHandler())
    
    현재 클라이언트가 새로운 노드에 GET 요청을 할 때, 그들은 {"timestamp":"1610378453927873100"}을 보게 될 것이다.물론 타임 스탬프는 요청한 시간에 근거하기 때문에 타임 스탬프가 바뀔 수 있습니다.{"message":"hey"}{"timestamp":"1610378453927873100"}의 패브릭이 서로 일치하지 않으므로 이를 통해 편리한 타사 응답이 요구 사항을 충족한다는 사실을 알 수 있습니다.이것이 바로 HTTP 중간부품이 작용하는 곳이다.

    요청을 가로막다


    중간부품의 핵심은 HTTP 프로세서다.그것은 입력, 요청, 선택할 수 있는 출력, 응답이 있습니다.출력은 선택할 수 있습니다. 왜냐하면 중간부품은 요청의 최종 목표가 아니기 때문입니다.그들은 중간자이기 때문에 보통 요청을 수정하여 다음 중간부품이나 처리 프로그램에 전달한다.
    그것들은 이렇게 보인다.
    func exampleMiddleware(h http.Handler) http.Handler {
       return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
          h.ServeHTTP(w, r)
       })
    }
    
    이 중간부품은 포장처리기를 되돌려줍니다. 이 처리기는 전달된 모든 처리기를 호출하기 때문에 이 중간부품을 사용하여 새로운 단점을 만들 수 있습니다. 아래와 같습니다.
    http.Handle("/fourth", exampleMiddleware(thirdPartyHandler()))
    
    권곡이라는 새로운 단점은 /third단점에서 우리가 기대하는 응답을 나타낼 것입니다.{"timestamp":"1610381096848493300"} . 그래서 HTTP 요청을 차단하는 예가 있습니다.우리는 요청을 수정하지 않았지만, 우리는 그것을 다른 처리 프로그램에 전달했다.
    요청을 어떻게 수정하는지 토론해 봅시다.이것은 우리가 필요할 때 어떤 처리 프로그램에 대한 접근을 제한할 수 있도록 하기 때문에 매우 강력하다.
    다음은 URL 매개 변수에서 키를 찾으려는 중간부품입니다. (예를 들어 웹사이트.com? 키=corgis는 귀엽습니다.)
    func restrictMiddleware(h http.Handler) http.Handler {
       return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
          if r.URL.Query().Get("super-secret-key") == "" {
             respBytes, err := json.Marshal(Response{Message: "key must not be empty"})
             if err != nil {
                panic(err)
             }
             w.WriteHeader(http.StatusBadRequest)
             _, err = w.Write(respBytes)
             if err != nil {
                panic(err)
             }
             return
          }
          h.ServeHTTP(w, r)
       })
    }
    
    이 중간부품의 새 끝점은 우리가 만든 첫 번째 처리 프로그램을 봉인합니다.
    http.Handle("/restrict", restrictMiddleware(exampleHandler()))
    
    현재 클라이언트가 키가 없는 상황에서 이 단점을 말아올릴 때, 그들은 {"message":"key must not be empty"}을 만날 것이다.
    키를 제공하려면 클라이언트가 curl 요청을 다음과 같이 포맷해야 합니다.
    curl -X "GET" http://localhost:9001/restrict?super-secret-key=secret
    
    그리고 고객은 예상 응답: {"message":"hey"}!
    이 예를 통해 서버가 목표 처리 프로그램의 처리 요청을 막아서 일부 노드에 대한 접근을 제한할 수 있음을 알 수 있다.

    차단 응답


    우리는 요청을 처리할 때 중간부품이 얼마나 유용한지 보았지만 제3자 처리 프로그램의 의외의 갑작스러운 응답과 관련이 있을 때 도와주지 못한다.적어도 기본적으로 그렇지 않다.
    우리가 제어할 수 없는 코드 블록의 응답을 차단하기 위해서, 우리는 응답의 완전성을 유지하는 동시에 메모리에서 응답을 포착해야 한다.우리는 응답이 완료되기 전에 그것을 고객에게 보내지 않을 것을 확보해야 한다.그렇지 않으면, 우리가 한 어떤 수정도 소용없다.
    기본적으로 http.ResponseWriter 인터페이스는 쓰기 방법만 지원하기 때문에 읽기를 지원하지 않습니다.우리는 http.ResponseWriter 인터페이스를 실현하는 사용자 정의 구조를 만들어서 우리가 원하는 기능을 실현할 수 있다.그러나 http.ResponseWriter에서 제공하는 기능을 완전히 포기하고 싶지 않으므로 다음과 같이 사용자 정의 구조에 응답 컴파일러를 필드로 포함해야 합니다.
    type responseSkimmer struct {
       http.ResponseWriter
       body bytes.Buffer
       header http.Header
       status int
    }
    
    func (rs *responseSkimmer) Header() http.Header {
       return rs.header
    }
    
    func (rs *responseSkimmer) Write(b []byte) (int, error) {
       rs.body.Reset()
       return rs.body.Write(b)
    }
    
    func (rs *responseSkimmer) WriteHeader(code int) {
       rs.status = code
    }
    
    다음에 우리는 중간부품에서 이'약독기'를 실현할 수 있다. 이 중간부품은 제3자 처리 프로그램을 매개 변수로 수신할 것이다.이것은 우리가 제3자 처리 프로그램에서 온 응답을 어떻게 포착하고 조종하며 새로운 주체를 사용하여 응답을 하는지 보여주는 것이다. 마치 아무 일도 일어나지 않은 것처럼.
    func responseMiddleware(h http.Handler) http.Handler {
       return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
          // Intercept response.
          skimmer := &responseSkimmer{
             ResponseWriter: w,
             body: bytes.Buffer{},
             header: http.Header{},
             status: 0,
          }
          h.ServeHTTP(skimmer, r)
    
          // Read.
          if len(skimmer.body.Bytes()) == 0 {
             skimmer.ResponseWriter.WriteHeader(http.StatusNoContent)
             _, err := skimmer.ResponseWriter.Write([]byte(""))
             if err != nil {
                panic(err)
             }
             return
          }
    
          // Transform.
          respBytes, err := json.Marshal(Response{Message: string(skimmer.body.Bytes())})
          if err != nil {
             panic(err)
          }
    
          // Write.
          skimmer.ResponseWriter.WriteHeader(skimmer.status)
          _, err = skimmer.ResponseWriter.Write(respBytes)
          if err != nil {
             panic(err)
          }
          return
       })
    }
    
    마지막으로, 우리는 단점을 추가하고 새로운 중간부품과 기존 처리 프로그램을 전달해야 한다.
    http.Handle("/intercept", responseMiddleware(thirdPartyHandler()))
    
    새로운 단점을 구부릴 때 우리는 제3자 처리 프로그램 단점 /third과 같은 응답을 볼 수 있지만 다른 것은 우리가 /example단점을 위해 만든 응답 구조 포장: {"message":"{\"timestamp\":\"1610599055718152700\"}"}이다.
    이제 우리는 그것을 가지고 있다.응답을 전달하는 데 사용되는 중간부품입니다. 요청만 전달하는 것이 아닙니다.구애!
    만약 네가 이 점을 할 수 있다면, 너의 독서에 감사한다.당신이 이곳에서 유용한 지식을 찾을 수 있기를 바랍니다.건배!

    크레디트


    표지 사진은 Florian Steciuk에서 Unsplash으로 발송됩니다!:D

    좋은 웹페이지 즐겨찾기