Elm的HTTP请求对于成功的请求返回NetworkError错误

17

我有丰富的使用React构建Web应用的经验,但想学习Elm。我已经花了几天时间解决一个HTTP请求问题。

我正在本地主机上运行一个Elm应用程序,地址为 localhost:8080,同时我的支持API位于 localhost:8081。无论我发起GET还是POST请求时,都会收到NetworkError错误。我一直在研究Elm JSON解码器,并认为这可能是我的问题所在,但我已经尝试从服务器发送简单的字符串并在我的Elm应用程序中使用Decode.string解码器,但仍然收到NetworkError错误。

以下是我的当前代码:

Commands.elm

module Commands exposing (..)

import Models exposing (..)
import Msg exposing (..)
import Http
import Json.Decode as Decode
import Json.Encode as Encode


createTempUser : Model -> Cmd Msg
createTempUser model =
  let
    tempUserBody =
      [ ( "firstname", Encode.string model.firstname )
      , ( "lastname", Encode.string model.lastname )
      , ( "phone", Encode.string model.phone )
      ]
        |> Encode.object
        |> Http.jsonBody

    url =
      myAPIUrl ++ "/endpoint"

    contentType = Http.header "Content-type" "text/plain"

    post =
      Http.request
        { method = "POST"
        , headers =  [contentType]
        , url = url
        , body = tempUserBody
        , expect = Http.expectJson decodeApiResponse
        , timeout = Nothing
        , withCredentials = False
        }

  in
    Http.send Msg.TempUserCreated post


decodeApiResponse : Decode.Decoder ApiResponse
decodeApiResponse =
  Decode.map4 ApiResponse
    (Decode.at ["status"] Decode.int)
    (Decode.at ["message"] Decode.string)
    (Decode.at ["created"] Decode.int)
    (Decode.at ["error"] Decode.string)


myAPIUrl : String
myAPIUrl = "http://localhost:8081"

模型.elm

module Models exposing (..)


type alias ApiResponse =
  { status: Int
  , message: String
  , created: Int
  , error: String
  }

消息.elm

module Msg exposing (..)

import Navigation exposing (Location)
import Models exposing (..)
import Http


type Msg
  = ChangeLocation String
  | OnLocationChange Location
  | CreateTemporaryUser
  | TempUserCreated ( Result Http.Error ApiResponse )

更新.elm

module Update exposing (..)

import Msg exposing (Msg)
import Models exposing (..)
import Routing exposing (..)
import Commands exposing (..)
import Navigation

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    Msg.ChangeLocation path ->
      ( { model | changes = model.changes + 1 }, Navigation.newUrl path )

    Msg.OnLocationChange location ->
      ( { model | error = "", route = parseLocation(location) }, Cmd.none )

    Msg.CreateTemporaryUser ->
      ( model, createTempUser model )

    Msg.TempUserCreated (Ok res) ->
       update (Msg.ChangeLocation signupCodePath) { model | httpResponse = toString(res) }

    Msg.TempUserCreated (Err err) ->
      ( { model | error = toString(err) }, Cmd.none )

Chrome的网络开发工具将响应显示为:

{"status":200,"message":"Successfully inserted temporary 
user","created":1518739596447,"error":""}

我认为这可能是所有相关的代码,但如果您需要查看更多内容,我会更新并包括所请求的代码。我承认我对 Elm 的 Json.Decode 库没有完全的理解,但我认为如果问题出在这里,我会收到一个包含更多上下文信息的 UnexpectedPayload 错误。


在我的 update Msg.TempUserCreated (Err err) 函数中,我将错误字符串保存到我的模型中,并在应用视图中显示我的模型,以便在发出请求时可以看到模型更新为 model.error = "NetworkError"。 - Michael Fox
1
我自己加载了仓库。由于我没有在 :8081 上运行 API 服务器,所以每个请求都会失败,就像你预期的那样。但是,我从未在模型中实际看到过 NetworkError! 模型中的错误始终为空。 我添加了一个 Debug.log,确信我实际上正在获得一个 NetworkError。如果没有 API 服务器,我可能无法重新创建这个错误。 - Sidney
谢谢您抽出时间来看一下! - Michael Fox
2
你在某个 JSON 对象中返回了 200,但是你是否在 HTTP 标头中得到了 200? - Simon H
1
@SimonH 好问题,有可能是服务器实际上发送了不同的状态。虽然理论上非成功状态应该引起“BadStatus”错误而不是“NetworkError”。 - Sidney
显示剩余6条评论
2个回答

14

@Sidney 和 @SimonH,

感谢你们帮我解决这个问题,很抱歉这最终不是 Elm 的问题。

最终的解决方案是在服务器上使用CORS中间件添加“Access-Control-Allow-Origin”头。 服务器仍然响应200状态,并包括整个成功的响应体,但这导致了Elm的Http结果失败。

再次感谢您的时间和想法!


1
很好,你分享了完整的代码,对于像我这样正在学习Elm的人来说,查看任何代码都是有用的 ;) - VGj.
2
这有点是 Elm 的问题,因为错误的原因,即缺少 CORS 标头,在 Elm 代码内部发出请求和接收 Elm 代码的 NetworkError 之间的某个地方被吞噬或丢失了。 - Philip Dorrell
如果你的回答是正确的,那么请接受它。这对于遇到相同问题的人非常有用。 - Titou

1

在开发过程中,您可以使用Firefox的CORS everywhere extension来快速解决问题,并将以下内容添加到扩展程序首选项的白名单中:

/^https?...localhost:8888\//i

对于Chrome浏览器,您可以使用此扩展程序


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接