Elm read HTTP response body for non-200 response - elm

How to read HTTP response body for a non 200 HTTP status
getJson : String -> String -> Effects Action
getJson url credentials =
Http.send Http.defaultSettings
{ verb = "GET"
, headers = [("Authorization", "Basic " ++ credentials)]
, url = url
, body = Http.empty
}
|> Http.fromJson decodeAccessToken
|> Task.toResult
|> Task.map UpdateAccessTokenFromServer
|> Effects.task
The above promotes the error from
Task.toResult : Task Http.Error a -> Task x (Result Http.Error a)
The value of which becomes
(BadResponse 400 ("Bad Request"))
My server responds with what is wrong with the request as a JSON payload in the response body. Please help me retrieve that from the Task x a into ServerResult below.
type alias ServerResult = { status : Int, message : String }

The Http package (v3.0.0) does not expose an easy way to treat HTTP codes outside of the 200 to 300 range as non-error responses. Looking at the source code, the handleResponse function is looking between the hardcoded 200 to 300 range
However, with a bit of copy and pasting from that Http package source code, you can create a custom function to replace Http.fromJson in order to handle HTTP status codes outside the normal "success" range.
Here's an example of the bare minimum you'll need to copy and paste to create a custom myFromJson function that acts the same as the Http package except for the fact it also treats a 400 as a success:
myFromJson : Json.Decoder a -> Task Http.RawError Http.Response -> Task Http.Error a
myFromJson decoder response =
let decode str =
case Json.decodeString decoder str of
Ok v -> Task.succeed v
Err msg -> Task.fail (Http.UnexpectedPayload msg)
in
Task.mapError promoteError response
`Task.andThen` myHandleResponse decode
myHandleResponse : (String -> Task Http.Error a) -> Http.Response -> Task Http.Error a
myHandleResponse handle response =
if (200 <= response.status && response.status < 300) || response.status == 400 then
case response.value of
Http.Text str ->
handle str
_ ->
Task.fail (Http.UnexpectedPayload "Response body is a blob, expecting a string.")
else
Task.fail (Http.BadResponse response.status response.statusText)
-- copied verbatim from Http package because it was not exposed
promoteError : Http.RawError -> Http.Error
promoteError rawError =
case rawError of
Http.RawTimeout -> Http.Timeout
Http.RawNetworkError -> Http.NetworkError
Again, that code snippet is almost entirely copy and pasted except for that 400 status check. Copying and pasting like that is usually a last resort, but because of the library restrictions, it seems to be one of your only options at this point.

Related

Receive message from an Elm process

I'm toying around with Elm processes in order to learn more about how they work. In parts of this, I'm trying to implement a timer.
I bumped into an obstacle, however: I can't find a way to access the result of a process' task in the rest of the code.
For a second, I hoped that if I make the task resolve with a Cmd, the Elm runtime would be kind enough to perform that effect for me, but that was a naive idea:
type Msg
= Spawned Process.Id
| TimeIsUp
init _ =
( Nothing
, Task.perform Spawned (Process.spawn backgroundTask)
)
backgroundTask : Task.Task y (Platform.Cmd.Cmd Msg)
backgroundTask =
Process.sleep 1000
-- pathetic attempt to send a Msg starts here
|> Task.map ( always
<| Task.perform (always TimeIsUp)
<| Task.succeed ()
)
-- and ends here
|> Task.map (Debug.log "Timer finished") -- logs "Timer finished: <internals>"
update msg state =
case msg of
Spawned id ->
(Just id, Cmd.none)
TimeIsUp ->
(Nothing, Cmd.none)
view state =
case state of
Just id ->
text "Running"
Nothing ->
text "Time is up"
The docs say
there is no public API for processes to communicate with each other.
I'm not sure if that implies that a process can't cummunicate with the rest of the app.
Is there any way to have update function receive a TimeIsUp once the process exits?
There is one way but it requires a port of hell:
make a fake HTTP request from the process,
then intercept it via JavaScript
and pass it back to Elm.
port ofHell : (() -> msg) -> Sub msg
subscriptions _ =
ofHell (always TimeIsUp)
backgroundTask : Task.Task y (Http.Response String)
backgroundTask =
Process.sleep 1000
-- nasty hack starts here
|> Task.andThen ( always
<| Http.task { method = "EVIL"
, headers = []
, url = ""
, body = Http.emptyBody
, resolver = Http.stringResolver (always Ok "")
, timeout = Nothing
}
)
Under the hood, Http.task invokes new XMLHttpRequest(), so we can intercept it by redefining that constructor.
<script src="elm-app.js"></script>
<div id=hack></div>
<script>
var app = Elm.Hack.init({
node: document.getElementById('hack')
})
var orig = window.XMLHttpRequest
window.XMLHttpRequest = function () {
var req = new orig()
var orig = req.open
req.open = function (method) {
if (method == 'EVIL') {
app.ports.ofHell.send(null)
}
return orig.open.apply(this, arguments)
}
return req
}
</script>
The solution is not production ready, but it does let you continue playing around with Elm processes.
Elm Processes aren't a fully fledged API at the moment. It's not possible to do what you want with the Process library on its own.
See the notes in the docs for Process.spawn:
Note: This creates a relatively restricted kind of Process because it cannot receive any messages. More flexibility for user-defined processes will come in a later release!
and the whole Future Plans section, eg.:
Right now, this library is pretty sparse. For example, there is no public API for processes to communicate with each other.

How spring webflux get whole request body?

I do post request,the content length is 817,bug get Body size is 610,How can i get whole body?ant that is need handle dynamic predicate by parameter!
Flux<DataBuffer> body = exchange.getRequest().getBody();
AtomicReference<String> bodyRef = new AtomicReference<>();
body.subscribe(dataBuffer -> {
CharBuffer charBuffer = Charsets.UTF_8.decode(dataBuffer.asByteBuffer());
DataBufferUtils.release(dataBuffer);
bodyRef.set(charBuffer.toString());
});
dynamic predicate
routes.route(api.getMethod() + ":" + api.getVersion(), r -> r
.predicate(exchange -> {
//params analyzing : Attributes.REQUEST_PARAMETER_METHOD
ParserUtils.parseRequest(exchange);
//handler ...
return api.getMethod().equals(exchange.getAttributes().get(Attributes.REQUEST_PARAMETER_METHOD));
}).uri(....)

What argument is Http.Request missing?

I'm trying to create an Http.Request value:
request : Http.Request
request =
{ verb = "POST"
, headers =
[ ( "Origin", "http://elm-lang.org" )
, ( "Access-Control-Request-Method", "POST" )
, ( "Access-Control-Request-Headers", "X-Custom-Header" )
]
, url = url
, body = body
}
The code is embedded in the function below:
tryRegister : Form -> (Result Http.Error JsonProfile -> msg) -> Cmd msg
tryRegister form msg =
let
url =
baseUrl ++ "register"
body =
encodeRegistration form |> Http.jsonBody
request : Http.Request
request =
{ verb = "POST"
, headers =
[ ( "Origin", "http://elm-lang.org" )
, ( "Access-Control-Request-Method", "POST" )
, ( "Access-Control-Request-Headers", "X-Custom-Header" )
]
, url = url
, body = body
}
in
Http.send msg request
I receive the following error:
Type Http.Request has too few arguments. - Expecting 1, but got 0.
What argument is Http.Request missing?
Appendix:
http://package.elm-lang.org/packages/evancz/elm-http/3.0.1/Http
Your appendix link is to an old version of the Http package. The new package is elm-lang/http. The type parameter that Request expects is the type to use in a successful response, which in your case looks like it should be JsonProfile.
The evancz/elm-http package is deprecated, you are probably actually using elm-lang/http so you're looking at the wrong documentation.
To construct a Http.Request using elm-lang/http you need to call the Http.request function and pass it a record describing the request.
http://package.elm-lang.org/packages/elm-lang/http/1.0.0/Http#request
Additionally you can't actually set the 'Origin' header for a request from within the browser this means you'll won't need to set any headers at all and can just use the Http.post helper function http://package.elm-lang.org/packages/elm-lang/http/1.0.0/Http#post

Get headers from http response

I am new to elm,
I have a login api which returns a JWT token in its hedears
curl http://localhost:4000/api/login?email=bob#example&password=1234
response:
HTTP/1.1 200 OK
authorization: Bearer eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXyLp0aSI6ImefP2GOWEFYWM47ig2W6nrhw
x-expires: 1499255103
content-type: text/plain; charset=utf-8
success
now Im trying to write a function that will send request and return the token from the headers in elm
authUser =
Http.send "http://localhost:4000/api/login?email=bob#example&password=1234"
how do I do this in a simple way?
In order to extract a header from a response, you will have to use Http.request along with the expectStringResponse function, which includes the full response including headers.
The expectStringResponse function takes a Http.Response a value, so we can create a function that accepts a header name and a response, then returns Ok headerValue or Err msg depending on whether the header was found:
extractHeader : String -> Http.Response String -> Result String String
extractHeader name resp =
Dict.get name resp.headers
|> Result.fromMaybe ("header " ++ name ++ " not found")
This could be used by a request builder like so:
getHeader : String -> String -> Http.Request String
getHeader name url =
Http.request
{ method = "GET"
, headers = []
, url = url
, body = Http.emptyBody
, expect = Http.expectStringResponse (extractHeader name)
, timeout = Nothing
, withCredentials = False
}
Here is an example on ellie-app.com which returns the value of content-type as an example. You can substitute "authorization" for your purposes.
May I humbly suggest you look at my elm-jwt library, and the get function there?
Jwt.get token "/api/data" dataDecoder
|> Jwt.send DataResult
JWT tokens normally need to be sent as a Authorization header and this function helps you create a Request type that can be passed to Http.send or Jwt.send

Elm, JSON decoder: How to decode an empty string?

What's the best way to handle an empty (no string at all) response?
Although the response code is 200, Elm returns an error because an empty response is not a valid JSON.
Here is my current code:
decodeAlwaysTrue : Json.Decode.Decoder Bool
decodeAlwaysTrue =
Json.Decode.succeed True
Http.send Http.defaultSettings httpConfig
|> Http.fromJson decodeAlwaysTrue
|> Task.perform FetchFail DeleteUserSuccess
EDIT1:
This a POST action so I can't use getString.
You could use the getString function from the Http module. That will give you back whatever string is returned from the HTTP request without attempting to convert is to a Json value.
If you instead need to use Http.send then you could do something like this:
Http.send Http.defaultSettings httpConfig
|> Task.perform FetchFail (always DeleteUserSuccess)
This assumes that DeleteUserSuccess is changed to be defined with no type parameter:
type Msg =
...
DeleteUserSuccess
It looks like you are never getting back a Json response so you'll probably be better using Http.getString
type Result = FetchFail Error
| DeleteUserSuccess
Http.getString address
|> Task.perform FetchFail (\s -> DeleteUserSuccess)
Since the successful get doesn't contain any information you can just ignore it and return DeleteUserSuccess regardless of the content of the string.