Как сделать запрос 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)
)
}
}