Как сделать запрос PUT из fetch в мой API go?

Я создаю REST API с помощью Go (с использованием Gorilla mux) и внешнего интерфейса с React. Запросы GET работают нормально, но у меня возникают проблемы с корректной работой запроса PUT. Это делает предварительный запрос OPTIONS успешным, но никогда не запросом PUT. Возможно, я неправильно обрабатываю его на сервере или неправильно выполняю запрос. Я создал промежуточное программное обеспечение, которое добавит заголовки CORS, потому что обработчики CORS из набора инструментов gorilla вообще не разрешают запрос OPTIONS. Я также попытался использовать axios вместо fetch, чтобы убедиться, что это не то, что я делал неправильно в запросе. Я получал точно такое же поведение с axios.

Вот маршрутизатор:

var V1URLBase string = "/api/v1"

func Load() http.Handler {

    r := mux.NewRouter().StrictSlash(true)

    // Status endpoints
    s := r.PathPrefix(fmt.Sprintf("%s%s", V1URLBase, "/statuses")).Subrouter()

    s.HandleFunc("/", handlers.GetStatuses).
        Methods("GET")
    s.HandleFunc("/{status_id}/", handlers.GetStatus).
        Methods("GET")
    s.HandleFunc("/", handlers.PostStatus).
        Methods("POST")
    s.HandleFunc("/{status_id}/", handlers.PutStatus).
        Methods("PUT")
    s.HandleFunc("/{status_id}/", handlers.DeleteStatus).
        Methods("DELETE")

    // Visit endpoints
    v := r.PathPrefix(fmt.Sprintf("%s%s", V1URLBase, "/visits")).Subrouter()

    v.HandleFunc("/", handlers.GetVisits).
        Methods("GET")
    v.HandleFunc("/{visit_id}/", handlers.GetVisit).
        Methods("GET")
    v.HandleFunc("/", handlers.PostVisit).
        Methods("POST")
    v.HandleFunc("/{visit_id}/", handlers.PutVisit).
        Methods("PUT")
    v.HandleFunc("/{visit_id}/", handlers.DeleteVisit).
        Methods("DELETE")

    // Member endpoints
    m := r.PathPrefix(fmt.Sprintf("%s%s", V1URLBase, "/members")).Subrouter()

    m.HandleFunc("/", handlers.GetMembers).
        Methods("GET")
    m.HandleFunc("/{member_id}/", handlers.GetMember).
        Methods("GET")
    m.HandleFunc("/", handlers.PostMember).
        Methods("POST")
    m.HandleFunc("/{member_id}/", handlers.PutMember).
        Methods("PUT")
    m.HandleFunc("/{member_id}/", handlers.DeleteMember).
        Methods("DELETE")

    // GymLocation endpoints
    gl := r.PathPrefix(fmt.Sprintf("%s%s", V1URLBase, "/gym_locations")).Subrouter()

    gl.HandleFunc("/", handlers.GetGymLocations).
        Methods("GET")
    gl.HandleFunc("/{gym_location_id}/", handlers.GetGymLocation).
        Methods("GET")
    gl.HandleFunc("/", handlers.PostGymLocation).
        Methods("POST")
    gl.HandleFunc("/{gym_location_id}/", handlers.PutGymLocation).
        Methods("PUT")
    gl.HandleFunc("/{gym_location_id}/", handlers.DeleteGymLocation).
        Methods("DELETE")

    router := ghandlers.LoggingHandler(os.Stdout, r)
    router = handlers.WriteCORSHeaders(r)

    return router
}

Вот обработчик CORS:

func WriteCORSHeaders(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("HIT")
        w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
        w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
        w.Header().Set(
            "Access-Control-Allow-Headers",
            "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization",
        )
        //w.Header().Set("Access-Control-Allow-Credentials", "true")

        if r.Method == "OPTIONS" {
            return
        }

        h.ServeHTTP(w, r)
    })
}

Вот обработчик PUT:

func PutVisit(w http.ResponseWriter, r *http.Request) {
    body, _ := ioutil.ReadAll(r.Body)
    r.Body.Close()

    visitId, err := strconv.ParseInt(mux.Vars(r)[VisitId], 10, 64)
    if err != nil {
        WriteJSON(w, http.StatusBadRequest, APIErrorMessage{Message: InvalidVisitId})
        return
    }

    visit := &models.Visit{}
    err = json.Unmarshal(body, visit)
    if err != nil {
        WriteJSON(w, http.StatusBadRequest, APIErrorMessage{Message: err.Error()})
        return
    }

    updated, err := datastore.UpdateVisit(visitId, *visit)
    if err != nil {
        WriteJSON(w, http.StatusInternalServerError, APIErrorMessage{Message: err.Error()})
        return
    }

    WriteJSON(w, http.StatusOK, updated)
}

func WriteJSON(w http.ResponseWriter, statusCode int, response interface{}) {
    encoder := json.NewEncoder(w)
    w.Header().Set("Content-Type", "application/json; charset=UTF-8")
    w.WriteHeader(statusCode)
    encoder.Encode(response)
}

Вот основной, который запускает сервер:

func main() {
    r := router.Load()

    http.ListenAndServe(":8080", r)
}

Вот мой запрос от Reactjs:

export function putVisit(visit) {
  return function(dispatch) {
    return fetch(`http://localhost:8080/api/v1/visits/${visit.visit_id}/`, {
      method: 'PUT',
      headers: {
        'Accept': 'application/json; charset=UTF-8',
        'Content-Type': 'application/json; charset=UTF-8'
      },
      body: JSON.stringify(visit)
    })
      .then(response => response.json())
      .then(json =>
        dispatch(updateVisit(json))
      )
      .catch(err =>
        console.log(err)
      )
  }
}

1 ответ

Решение

В случае, если кто-то сталкивался с подобной проблемой, я смог заставить это работать, добавив заголовок JSON к моей функции CORS (вместо функции WriteJSON) следующим образом:

func CORS(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json; charset=UTF-8")
        w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
        w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
        w.Header().Set(
            "Access-Control-Allow-Headers",
            "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization",
        )
        w.Header().Set("Access-Control-Allow-Credentials", "true")

        if r.Method == "OPTIONS" {
            return
        }
        h.ServeHTTP(w, r)
    })
}

После того, как я добавил это, запрос все еще не работал с fetch. Итак, я переключился, попробовал снова с axios, и это сработало. Вот как выглядит новый код запроса с axios.

export function putVisit(visit) {
  return function(dispatch) {
    return axios.put(`http://localhost:8080/api/v1/visits/${visit.visit_id}/`, visit)
      .then(response =>
        dispatch(updateVisit(response.data))
      )
      .catch(err =>
        console.log(err)
      )
  }
}
Другие вопросы по тегам