Requests: HTTP Requests Made Simple

Hello everyone! I’ve started working on requests and I’d appreciate stars :star2: and especially comments :speech_balloon: on making it better.

Basically, requests simplify the tasks of marshalling data in HTTP requests for you, including asynchronous call like GetAsync so you don’t have to spawn goroutines explicitly.

I really appreciate feedback from you all. Thank you. :pray:

The first thing that strikes me is that Go does not have optional function parameters, yet you’re emulating them with a ...interface{} parameter. This is very unidiomatic in Go and makes me suspicious right off the bat. :wink:

Your GetAsync doesn’t handle error returns from client.Do - it’ll discard the error and leave the client hanging forever on the request channel. I looked at this one because, again, it’s quite unidiomatic to provide an async API when the API user can quite simply call your Get in a goroutine and correctly manage things like errors and channel buffers themselves.

2 Likes

Thanks @calmh. Yes I thought it was unidiomatic as well. I’ll take a look at GetAsync but then to be idiomatic is to just use net/http :grinning:

Well, yes. :wink: I mean by all means do wrap it, but I’d usually expect to see that when used for a specific purpose - say, a Get that always expects a JSON response and parses it before returning.

One slightly more common way to handle optional arguments, to a constructor or a thing like your Get, is an options struct:

type Options {
  Headers map[string]string
  Auth struct {
    Username string
    Password string
  }
  // ...
}

func Get(url string, opts &Options) ... {
    // ...
}

func main() {
   resp, err := Get("http://example.com", nil) // no options
   // ...
   resp, err = Get("http://example.com/secret", &Options{Auth.User: "foo", Auth.Password: "bar"}) // with auth
  // ...
}

Why is it not “recommended” to use ...interface{}, apart from being the pain in the doc? For instance, API from dabase/sql uses that a lot?

Actually the opt struct looks way cleaner and would totally prevent users from filling in nil just to get to the last argument. This is awesome!

Also consider functional options: http://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis

Untyped options are brittle and defy compile-time type checking.

2 Likes

Thanks for the feedback @elithrar

The primary reasons not to use ...interface{} are (a) you lose all compile-time type safety, and (b) you lose all compile-time argument checking.

Yet another way to handle multiple optional arguments without losing type safety is chainable methods to set options on an object. Each method sets a single option, and returns the original object it set the option on. Then you can do something like

resp, err := NewRequest("http://example.com").Auth("user", "password").Accept("application/json").Get()

You can store a cumulative error list in a field of the object, so that the final Get() (or whatever) method can return any errors that occurred earlier in the chain.

1 Like

That’s one interesting way to solve it, though I’m not really a fan of chaining. It seems like the simplest way one can get.

@elithrar functional options seem to be the right way to go for me. It’s the most transparent and clearly make use of first-class functions in Go.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.