go-swagger / go-swagger

Swagger 2.0 implementation for go
https://goswagger.io
Apache License 2.0
9.48k stars 1.25k forks source link

Generated client does not accept content types specified in schema #1244

Open sidh opened 6 years ago

sidh commented 6 years ago

Problem statement

If some content type like "image/png" or "/" is specified in "produces" part schema, generated client does not accept those content types and errors out with 'no consumer: "image/png"'.

Specifically, we use a schema like the one below and respond to user requests with binary data and set content-type header manually based on mime-type of the data. It works fine if you access it from curl or web browser but errors in go-swagger generated client.

Swagger specification

swagger: "2.0"
info:
  title: Test
  version: 0.0.1
schemes:
- "http"
paths:
  /test:
    get:
      produces:
        - "application/octet-stream"
        - "*/*"
      responses:
        200:
          description: 200 response
          headers:
            Content-Type:
              type: string
          schema:
            type: string
            format: byte
casualjim commented 6 years ago

You don't strictly need to specify the Content-Type header.

I think it should work if you define your spec like:

swagger: "2.0"
info:
  title: Test
  version: 0.0.1
schemes:
- "http"
paths:
  /test:
    get:
      produces:
        - "application/octet-stream"
      responses:
        200:
          description: 200 response
          schema:
            type: string
            format: binary

When it comes to unknown mime types and not having producers or consumers, those are stored in a map on the client and you can add your own. They are not meant to be a complete list although we could/should probably add a few more.

https://github.com/go-openapi/runtime/blob/master/client/runtime.go#L140-L151

sidh commented 6 years ago

I need to specify correct content-type so web browser is able to pick it up as correct content and display/act accordingly.

What I guess I really need is a default consumer for generated client that if set, acts as a fallback option (most likely it will just act as a default ByteStreamConsumer). I need that since I cannot specify beforehand which content-types will be used.

Somewhere here: https://github.com/go-openapi/runtime/blob/master/client/runtime.go#L324-L327

fredbi commented 6 years ago

See also #1284

MrSaints commented 6 years ago

This is still an on-going problem. Indeed, using application/octet-stream is a solution, but there are cases much like the one OP described with images where we do want the client to behave like application/octet-stream despite the specified content type.

As a temporary workaround, I modified the generated NewHTTPClientWithConfig in the _client.go file to include:

    transport.Consumers["image/jpeg"] = runtime.ByteStreamConsumer()
    transport.Consumers["image/png"] = runtime.ByteStreamConsumer()
    transport.Consumers["image/webp"] = runtime.ByteStreamConsumer()
    transport.Consumers["image/gif"] = runtime.ByteStreamConsumer()

Works without any problems :)

casualjim commented 6 years ago

fwiw you can edit that in your own application code without modifying the generated code. You will just need to use a different constructor, there is a reason those fields are exported :)

here's an example where I build a client to actually reuse tcp connections and I've added a consumer in there too so you see where to do it.

func (c *clusterFlags) NewAPIClient() (*bhclient.BuildingHistory, error) {
    clientTlsOpts, err := httptransport.TLSClientAuth(httptransport.TLSClientOptions{
        CA:          c.ClientCACertificate,
        Certificate: c.ClientCertificate,
        Key:         c.ClientCertificateKey,
    })
    if err != nil {
        return nil, fmt.Errorf("TLS config: %v", err)
    }

    transp := httptransport.KeepAliveTransport(&http.Transport{
        Proxy: http.ProxyFromEnvironment,
        DialContext: (&net.Dialer{
            Timeout:   30 * time.Second,
            KeepAlive: 30 * time.Second,
            DualStack: true,
        }).DialContext,
        MaxIdleConns:          100,
        IdleConnTimeout:       90 * time.Second,
        TLSHandshakeTimeout:   10 * time.Second,
        ExpectContinueTimeout: 1 * time.Second,
        TLSClientConfig:       clientTlsOpts,
    })

    schemes := bhclient.DefaultSchemes

    bt := httptransport.New(c.JoinURI().Host, bhclient.DefaultBasePath, schemes)
    // further configure go-swagger runtime.Transport
    bt.Consumers["image/jpeg"] = runtime.ByteStreamConsumer()
    bt.Transport = transp
    return bhclient.New(bt, nil), nil
}