Giter VIP home page Giter VIP logo

elm-http's Introduction

elm-http's People

Contributors

amcvega avatar capisce avatar davis avatar evancz avatar jgillich avatar jmhain avatar process-bot avatar theseamau5 avatar thsoft avatar yogsototh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

elm-http's Issues

Return response body with bad responses

For some APIs, 400 responses contain an error explaining why the request was bad, which you may want to display. It's possible to get at this with send, but it would be nice if it were just in the BadResponse constructor in the Error type.

Introduce a `Method` type for `verb`

Currently, the verb field in Request takes a String.

type alias Request = 
    { verb : String
    , headers : List (String, String)
    , url : String
    , body : Body
    }

This could be made more type-safe by using a Method type similiar to how e.g.
Snap does it:

type Method 
  = GET
  | HEAD
  | POST
  | PUT
  | DELETE
  | TRACE
  | OPTIONS
  | CONNECT
  | PATCH
  | Method String

On a side note, I think verb should be renamed to method as
the latter term seems to be more widely used, cf. RxJS.

Certain url strings crash program with vague ":" error; no request is sent

No Error in the following cases:

"abc"
"abc.abc"
"abc/abc:123"
"123.123.123.123:123"
"123:123"
"123abc:123"

Error is thrown and no network call is made in the following cases:

"abc:abc"
"abc:123"
"abc.abc:123"
"abc123:123"
"abc-abc:123"
"abc-123:123"

In elm 0.17 the error is limited to ':', in elm 0.16 you also get many counts of the "Error: The notify function has been called synchronously! This can lead to frames being dropped." referenced in a number of issues including this one. This happens in reactor or via compiler.

What caught me (for longer than I'd like to admit) was using string: "localhost:123/foo/bar"--an easy mistake during development. Omitting the port, say "github.com/foo/bar", the request would have gone through as a relative URL (with a 404) and the bug/missing protocol would have been easy to spot.

I'm happy to open a PR, but I wanted to get some consensus on the right way to go about it. My suggestion would be to append "/" to the beginning any of the offending matches to avoid the error. Any concerns about potential side effects?

Elm has such great error reporting that it seems like this is worth fixing--it's a rare instance where something is going wrong and there's clear path on what to do.

Http.post cannot handle empty response body

The single insert API of postgrest returns an empty response body (actual information, a redirect URL, is in one of the response headers).

This doesn't work well with Http.post, because none of the json decoders will successfully parse an empty body. The Json.Decode module doesn't export any function that allows us to bypass empty input:

> Json.Decode.decodeString (Json.Decode.null 0) ""
Err ("Unexpected end of input") : Result.Result String number
> Json.Decode.decodeString (Json.Decode.succeed 0) ""
Err ("Unexpected end of input") : Result.Result String number
>

Solutions

  1. Implement Json.Decode.always : a -> Decoder a and pass it to Http.post (Unlike succeed, always will ignore the input without creating any parse errors).
  2. Implement a emptyDecoder in elm-http, and invoke Http.post Http.emptyDecoder ....

Any thoughts on this? It won't be ideal for me to fork the implementation of post and directly invoke send and handle errors.

Request: special error when failure is due to cross origin permissions

For reference: elm/package.elm-lang.org#50

If you use Http.getString to get a URL from a different domain than your elm code is being run on and the server serving the URL does not have CORS turned on, a runtime exception, e.g.

XMLHttpRequest cannot load http://package.elm-lang.org/packages/elm-lang/core/latest/README.md. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8000' is therefore not allowed access.

is raised

My thought is it should be captured in the the Http.Error of the returned Task

Proposal: add JSONP mechanism

I know, JSONP is basically a hack to circumvent dealing with CORS inside browsers. It executes whatever Javascript is sent back, so it is also unsafe. But there still are many public APIs that are only consumable using JSONP (or you're forced to build a proxy server). It doesn't even necessarily have to be a public API. Simply if you're stuck with an API that you have to consume that has strict CORS headers, it most likely will support JSONP.

Proposal

Add two functions to elm-http for dealing with JSONP so we can leverage the usage of Tasks. One is a Native function, the other is an Elm function that accepts a decoder.

jsonp : String -> String -> String -> Task x String
jsonp =
    Native.Send.jsonp


jsonpGet : Json.Decoder value -> String -> String -> Task Error value
jsonpGet decoder url callbackParam =
    let
        decode s =
            Json.decodeString decoder s
                |> Task.fromResult
                |> Task.mapError Http.UnexpectedPayload
    in
        randomCallbackName
            `Task.andThen` jsonp url callbackParam
            `Task.andThen` decode

As you can see, there is no err handling for JSONP. This is because there is no way of knowing whether it will fail. You load the contents into a HTML <script> tag and let your browser take the wheel. I am sure that 99% of the time, JSONP will be used to parse JSON. So if there is any wrong output being generated in the <script> tag, the decode should fail with Http.UnexpectedPayload.

JSONP circumvents CORS by loading javascript wrapped in a callback into a <script> tag. <script> tags can load any URL (barring HTTP/HTTPS discrepancies). The important thing is the callback. There could be multiple JSONP requests going on parallel, so we need to generate a global callback function that is unique for every JSONP Task. We use the Random module for this. The function randomCallbackName could look like this:

import Random
import Time

randomCallbackName : Task x String
randomCallbackName =
    let
        generator =
            Random.int 100 Random.maxInt
    in
        Time.now
            |> Task.map (Random.step generator Random.initialSeed)
            |> Task.map (fst >> toString >> (++) "callback")

We're left with a random callback name that starts with "callback" and has a random number added to it. This random callback name will be used in the JS side. This is what the JS side could look like.

var _evancz$elm_http$Native_Http = function() {

  function jsonp(url, callbackParam, callbackName)
  {
    return _elm_lang$core$Native_Scheduler.nativeBinding(function(callback) {
      window[callbackName] = function(content)
      {
        callback(_elm_lang$core$Native_Scheduler.succeed(JSON.stringify(content)));
      };

      var scriptTag = createScript(url, callbackParam, callbackName);
      document.head.appendChild(scriptTag);
      document.head.removeChild(scriptTag);
    });
  }


  function createScript(url, callbackParam, callbackName)
  {
    var s = document.createElement('script');
    s.type = 'text/javascript';

    if (url.indexOf('?') >= 0)
    {
      s.src = url + '&' + callbackParam + '=' + callbackName;
    }
    else {
      s.src = url + '?' + callbackParam + '=' + callbackName;
    }

    return s;
  }


  return {
    jsonp: F3(jsonp)
  };

}();

We create a global function using window[callbackName]. This function receives the content from the JSONP call. To avoid doing anything unsafe, we JSON.stringify the callback contents and pass it to the Scheduler with succeed. The script tag gets created with the HTTP call URL, and it is appended and removed from the document <head>.

Discussion points

  1. JSONP is a hack to circumvent CORS. Do we want to support something like this in Elm?
  2. It loads any Javascript returned from the server into a script tag, so it could be unsafe. In this implementation, I use JSON.stringify on the callback contents.
  3. This is a rough mockup. I'm already using this code in a project of mine, and it's working great. Maybe some naming could be changed?

Refused to set unsafe header "Origin" in Chrome

If you attempt to use the sample code from Http.send

crossOriginGet : String -> String -> Task RawError Response
crossOriginGet origin url =
  Http.send defaultSettings
    { verb = "GET"
    , headers = [("Origin", origin)]
    , url = url
    , body = empty
    }

running the elm code in Chrome (Version 43.0.2357.134 (64-bit) on OSX 10.10.4) gives a javascript error:

Refused to set unsafe header "Origin"

Tried it on firefox and it does not seem to be throwing the same error.

High level requests for verbs other than POST/GET

Currently, there are only two "high-level" HTTP functions, get and post. It seems to me that PUT and DELETE would also be very useful. What do you think?

Parenthetically, the Consul HTTP API supports GET, PUT, and DELETE.

I'm currently using the following implementation of putString, delString:

{-| Mirror the getString API, but for PUT requests.                             
-}                                                                              
httpPutString : String -> String -> Task Error String                           
httpPutString =                                                                 
  wrapHttpApi "PUT"                                                             


{-| Mirror the getString API, but for DELETE requests.                          
-}                                                                              
httpDelString : String -> Task Error String                                     
httpDelString =                                                                 
  flip (wrapHttpApi "DELETE") ""                                                


{-| Create a high-level API for some HTTP verb other than GET/POST              
-}                                                                              
wrapHttpApi : String -> String -> String -> Task Error String                   
wrapHttpApi verb url body =                                                     
  let                                                                           
    request =                                                                   
      { verb = verb, headers = [], url = url, body = Http.string body }         
  in                                                                            
    Task.mapError promoteError (Http.send Http.defaultSettings request)         
      `andThen` handleResponse Task.succeed     

with handleResponse and promoteError copied directly from this module's source.

onStart / onProgress only good for Debug.log ?

  • onStart and onProgress allow you to monitor progress. This is useful if you want to show a progress bar when uploading a large amount of data.

    , onStart : Maybe (Task () ())
    , onProgress : Maybe (Maybe { loaded : Int, total : Int } -> Task () ())

How can one update the model with the 'loaded' and 'total' values here? I don't see how the callbacks could be used for anything other than Debug.log and succeed () and can't find any examples to the contrary. Shouldn't the signature be Task () (Cmd msg) or am I just not understanding how to trigger an update from these callbacks?

Allow retrieval of JSON response body for non-200 HTTP responses

Hi, Could you help me read HTTP response body for a non 200 HTTP status

getJson : String -> String -> Effects Action
getJson url credentials =
  Http.send Http.defaultSettings
    { verb = "POST"
    , 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 }

A returned 302 redirects the page.

Hey,

One of the endpoints I need to hit returns a 302. This response is redirecting away from my current elm page. Shouldn't Http.post return that as an error instead?

`uriDecode` is not total.

uriDecode can fail at runtime

Depending on the value of the string, this function may fail.

For example:

uriDecode "%"

Will throw the following error at runtime

Uncaught URIError: URI malformed
    at decodeURIComponent (native)
    at <anonymous>:1:1

Expected behavior

Function is total and does not have the potential to throw errors.

Recommendation

Perhaps the type of this function should be String -> Result URIError String

No FormData in Node.js

Is it possible to have this module work in Node.js? I run my test suite using Node.js and was somewhat puzzled, when merely constructing a message with Http.multipart caused a ReferenceError: FormData is not defined.

Maybe I am missing something, but it looks like FormData is simply not present in node. Maybe this package could help?

`Http.getString` only works for http://httpbin.org/get

When running the following program in elm-reactor (or using elm-make for that matter), the only webpage I have found that it actually GETs is httpbin. Otherwise, I get a NetworkError. I am utterly befuddled as to why this might be, can anyone point out my error?

    module Main (..) where

    import Http
    import Html exposing (..)
    import Effects
    import Html.Events as Events
    import StartApp
    import Task


    -- MODEL


    type alias Model =
      { output : String }


    type Action
      = Response String
      | Request String
      | HTTPError Http.Error



    -- UPDATE


    update : Action -> Model -> ( Model, Effects.Effects Action )
    update act mod =
      case act of
        Response str ->
          ( { mod | output = str }, Effects.none )

        Request srv ->
          let
            effects =
              srv
                |> Http.getString
                |> Task.map Response
                |> flip Task.onError (Task.succeed << HTTPError)
                |> Effects.task
          in
            ( { mod | output = "GET: " ++ srv }, effects )

        HTTPError err ->
          ( { mod
              | output =
                  "Error: "
                    ++ case err of
                        Http.Timeout ->
                          "Timeout"

                        Http.UnexpectedPayload str ->
                          "Unexpected payload: " ++ str

                        Http.BadResponse code str ->
                          "Bad response: " ++ toString code ++ ": " ++ str

                        Http.NetworkError ->
                          "Network error"
            }
          , Effects.none
          )



    -- VIEW


    view : Signal.Address Action -> Model -> Html.Html
    view address mod =
      div
        []
        [ div
            []
            [ input
                [ Events.on "input" Events.targetValue (Request >> Signal.message address) ]
                []
            ]
        , div [] [ text mod.output ]
        ]



    -- MAIN


    app : StartApp.App Model
    app =
      StartApp.start
        { init = ( { output = "No requests made" }, Effects.none )
        , update = update
        , view = view
        , inputs = []
        }


    main =
      app.html


    port tasks : Signal (Task.Task Effects.Never ())
    port tasks =
      app.tasks

HTTP/2 support

Has it already been implemented the HTTP2 support with push feature?

HTTP requests on different ports

How do you make an HTTP request to a port other than 80? Do you just feed a different URL, e.g. getString "100.10.10.10:8080/endpoint"? If so, it might be worth adding this to the examples.

Body not being sent with "GET" requests

I have the following code:

fetchAll : Cmd AppMsg
fetchAll =
  let
    value = getArtifactsRequestEncoded 1
    body = Http.jsonBody value
    request = Http.request
      { method = "PUT"
      , headers =
        [ Http.header "Content-Type" "application/json"
        ]
      , url = fetchAllUrl
      , body = body
      , expect = Http.expectJson getArtifactsResponseDecoder
      , timeout = Nothing
      , withCredentials = False
      }
  in
    Http.send newArtifactsMsg request

If I switch method = "PUT" to method = "GET" this no longer sends any data in the body!

For reproducability, the bug was found in my project at this commit. The elm app is in web-ui and my MVP backend is written in rust in web-ui/api. However, I wouldn't advise you go to the trouble of reproducing (let me know if you need more info).

CORS Example does not work

Hi, tried the CORS example from the docs here:

    corsPost : Request
    corsPost =
        { verb = "POST"
        , headers =
            [ ("Origin", "http://elm-lang.org")
            , ("Access-Control-Request-Method", "POST")
            , ("Access-Control-Request-Headers", "X-Custom-Header")
            ]
        , url = "http://example.com/hats"
        , body = empty
        }

It does not work, my browser sends options, as if it's trying to do a preflight request. Does Elm not manage CORS as xhrRequest does now automatically in JS ?

fromString

fromJson is nice because it abstracts away promoteError and handleResponse. I have another super common use case which is just returning a plaintext string from a response and then using that value provided the task succeeds.

File Uploads?

Any Idea on how I'm going to do file uploads. I was searching the source and could only find multipart string upload? Will that be useful?

post can't send json

You elm-http library is rightly geared towards json. Both get and post pass the returned values through json decoders.

However, sending json to a server is effectively not supported - in particular a standard node/express setup does not pick up a json string posted by Http.post. This is because the headers for send are hard coded to [], while Express expects [("Content-type", "application/json")]. Several Elm users have had similar difficulties - see https://groups.google.com/forum/#!topic/elm-discuss/Zpq9itvtLEY

According to stackoverflow "application/json" is clearly the right choice for json data.

Note also this from the W3C:

The content type "application/x-www-form-urlencoded" is inefficient for sending large quantities of binary data or text containing non-ASCII characters. The content type "multipart/form-data" should be used for submitting forms that contain files, non-ASCII data, and binary data.

As things stand i think there are four options for:

  1. do nothing, at which point I'm not sure anyone can make use of Http.post
  2. make [("Content-type", "application/json")] the default
  3. make [("Content-type", "application/x-www-form-urlencoded")]
  4. refactor Http.post to take a Content-Type parameter.

To my mind 2 and 4 have the strongest claim. Let me know what you think and i can submit a PR.

Support for elm 0.18

Error: I cannot find a set of packages that works with your constraints.

--> There are no versions of evancz/elm-http that work with Elm 0.18.0. Maybe the maintainer has not updated it yet.

Is the documentation missing `exposing`?

The examples in documentation look like

import Json.Decode (list, string)

hats : Task Error (List String)
hats =
    get (list string) "http://example.com/hat-categories.json"

Given the compiler warning, it seems like the correct code would be

import Json.Decode exposing (list, string)

hats : Task Error (List String)
hats =
    get (list string) "http://example.com/hat-categories.json"

instead?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.