Skip to content

HTTP-GET header Content-Type is not set, cause server check error #2254

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
jjqq2013 opened this issue Feb 26, 2016 · 14 comments
Closed

HTTP-GET header Content-Type is not set, cause server check error #2254

jjqq2013 opened this issue Feb 26, 2016 · 14 comments

Comments

@jjqq2013
Copy link

Thanks swagger api!
I found JavaClientCodegen has a problem.
When i define consumers in yaml, it means send request with the specified Content-Type, but unfortunately, HTTP GET does not works, POST and others works.

Sample yaml:

# Example YAML to get you started quickly.
# Be aware that YAML has indentation based scoping.
# Code completion support is available so start typing for available options.
swagger: '2.0'

# This is your document metadata
info:
  version: "0.0.0"
  title: <enter your title>

produces:
  - application/mycom-v1+json
consumes:
  - application/mycom-v1+json

# Describe your paths here
paths:
  # This is a path endpoint. Change it.
  /persons:
    # This is a HTTP operation
    get:
      # Describe this verb here. Note: you can use markdown
      description: |
        Gets `Person` objects.
        Optional query param of **size** determines
        size of returned array
      # This is array of GET operation parameters:
      parameters:
        # An example parameter that is in query and is required
        -
          name: size
          in: query
          description: Size of array
          required: true
          type: number
          format: double
      # Expected responses for this operation:
      responses:
        # Response code
        200:
          description: Successful response
          # A schema describing your response object.
          # Use JSON Schema format
          schema:
            title: ArrayOfPersons
            type: array
            items:
              title: Person
              type: object
              properties:
                name:
                  type: string
                single:
                  type: boolean

Result ClientAPI.java

private ClientResponse getAPIResponse(String path, String method, List<Pair> queryParams, Object body, Map<String, String> headerParams, Map<String, Object> formParams, String accept, String contentType, String[] authNames) throws ApiException {
    if (body != null && !formParams.isEmpty()) {
      throw new ApiException(500, "Cannot have body and form params");
    }

    updateParamsForAuth(authNames, queryParams, headerParams);

    final String url = buildUrl(path, queryParams);
    Builder builder;
    if (accept == null) {
      builder = httpClient.resource(url).getRequestBuilder();
    } else {
      builder = httpClient.resource(url).accept(accept);
    }

    for (String key : headerParams.keySet()) {
      builder = builder.header(key, headerParams.get(key));
    }
    for (String key : defaultHeaderMap.keySet()) {
      if (!headerParams.containsKey(key)) {
        builder = builder.header(key, defaultHeaderMap.get(key));
      }
    }

    ClientResponse response = null;

    if ("GET".equals(method)) {
      response = (ClientResponse) builder.get(ClientResponse.class);   //**** did not use contentType
    } else if ("POST".equals(method)) {
      response = builder.type(contentType).post(ClientResponse.class, serialize(body, contentType, formParams));
    } else if ("PUT".equals(method)) {
      response = builder.type(contentType).put(ClientResponse.class, serialize(body, contentType, formParams));
    } else if ("DELETE".equals(method)) {
      response = builder.type(contentType).delete(ClientResponse.class, serialize(body, contentType, formParams));
    } else {
      throw new ApiException(500, "unknown method type " + method);
    }
    return response;
  }

As you can see above code, contentType is not used for HTTP GET.

When it goes to a spring framework server, the problem occours:
2016-02-26 14 20 19

So, the server did right thing, but client did wrong. I need client send Content-Type for HTTP GET.

Would you confirm this problem?

@jjqq2013
Copy link
Author

I added three lines to auto-generated ApiClient.java, then it works

Old code begins:

  private ClientResponse getAPIResponse(String path, String method, List<Pair> queryParams, Object body, Map<String, String> headerParams, Map<String, Object> formParams, String accept, String contentType, String[] authNames) throws ApiException {
    if (body != null && !formParams.isEmpty()) {
      throw new ApiException(500, "Cannot have body and form params");
    }

    updateParamsForAuth(authNames, queryParams, headerParams);

    final String url = buildUrl(path, queryParams);
    Builder builder;
    if (accept == null) {
      builder = httpClient.resource(url).getRequestBuilder();
    } else {
      builder = httpClient.resource(url).accept(accept);
    }

Insert following code:

    if ("GET".equals(method) && contentType != null && contentType.length() > 0) {
      headerParams.put("Content-Type", contentType);
    }

Continue old code:

    for (String key : headerParams.keySet()) {
      builder = builder.header(key, headerParams.get(key));
    }
    for (String key : defaultHeaderMap.keySet()) {
      if (!headerParams.containsKey(key)) {
        builder = builder.header(key, defaultHeaderMap.get(key));
      }
    }

    ClientResponse response = null;

    if ("GET".equals(method)) {
      response = (ClientResponse) builder.get(ClientResponse.class);
    } else if ("POST".equals(method)) {
      response = builder.type(contentType).post(ClientResponse.class, serialize(body, contentType, formParams));
    } else if ("PUT".equals(method)) {
      response = builder.type(contentType).put(ClientResponse.class, serialize(body, contentType, formParams));
    } else if ("DELETE".equals(method)) {
      response = builder.type(contentType).delete(ClientResponse.class, serialize(body, contentType, formParams));
    } else {
      throw new ApiException(500, "unknown method type " + method);
    }
    return response;
  }

@fehguy
Copy link
Contributor

fehguy commented Feb 26, 2016

But Content-Type is not required with GET requests. Only POST, etc. where you're sending a body.

@jjqq2013
Copy link
Author

dear sir, I agree with you, but once define consumes property, the spring framework check request content-type, even HTTP GET. I can not accuse spring guys, because HTTP specification not strictly say no Content-Type for HTTP-GET, just SHOULD.

In other words, Anyone can legally send HTTP-GET with Content-Type and empty body or form! I hav did the experiment use ajax or nodejs. There were many same question about Content-Type and HTTP-GET coexistence in stackoverflow.

To resolve this problem, the only way: add Content-Type to HTTP-GET in client code

@jjqq2013
Copy link
Author

The problem is : once user define "consumes" type in yaml, swagger's auto-generated java server and java client will not works.

Other type such as nodejs ApiClient.js, it did set Content-Type in HTTP-GET request.

Would you please consider this problem again?

@wing328
Copy link
Contributor

wing328 commented Feb 27, 2016

Have you tested Java API client using other HTTP library (not the default Jersey1.x)?

You can do so by passing -Dlibrary=jersey2 to the command line. For a full list of HTTP libraries supported, please refer to the help page.

@jjqq2013
Copy link
Author

i have not tested jersey2, but i have chekced jersey2's ApiClient.java, same problem there. My project do not want to specify extra option to use jersey2.
I will make a test case to prove the problem.

@wing328
Copy link
Contributor

wing328 commented Feb 27, 2016

Do you mind testing other Java HTTP libraries to see if there are similar issue? If yes, we want to fix those as well
Supported HTTP libraries in Java: retrofit, retroit2, okhttp-gson, feign, jersey2

@jjqq2013
Copy link
Author

Ok, i will try others.

2016年2月27日土曜日、[email protected]さんは書きました:

Do you mind testing other Java HTTP libraries to see if there are similar
issue? If yes, we want to fix those as well
Supported HTTP libraries in Java: retrofit, retroit2, okhttp-gson, feign,
jersey2


Reply to this email directly or view it on GitHub
#2254 (comment)
.

@ePaul
Copy link
Contributor

ePaul commented Feb 28, 2016

Sending a Content-Type where there is no body (other than in a response to HEAD) sounds wrong.

RFC 7231, Section Content-Type:

The "Content-Type" header field indicates the media type of the
associated representation: either the representation enclosed in the
message payload or the selected representation, as determined by the
message semantics.

(This "selected representation" is for responses to HEAD requests.)

I think the top-level consumes property should certainly not apply to GET (or HEAD) requests.
And if Spring-MVC requires a Content-Type for a GET request, then either there is some bug or you configured it wrong.

(And maybe there is a bug in the Swagger-generator for Spring-MVC.)

@jjqq2013
Copy link
Author

@ePaul I understand.
YAML does not allow me to define "consumes" (Request's Content-Type) in POST.

swagger: '2.0'
info:
  version: "0.0.0"
  title: <enter your title>

produces:
  - application/mycom-v1+json

paths:
  /persons:
    post:
      #i want to web server check following request's content-type. But syntax error!
      consumes:
        -application/mycom-v1+json
      description: |
        Gets `Person` objects.
      parameters:
        -
          name: size
      responses:
        200:
          description: Successful response
          schema:
            title: ArrayOfPersons
            type: array
            items:
              title: Person
              type: object
              properties:
                name:
                  type: string

So i have to define "consumes" globally, for any HTTP type.
It in turn cause web server's check cause error.

As you can see in above screenshot in first post,
it is spring frame work (NOT swagger's framework) that check request's Content-Type even if no HTTP body, such as when HTTP GET.

So, we have three choice:

1. Modify spring framework's check logic: ignore content-type check if no body.

2. Modify swagger's auto-generated client logic to add Content-Type to request unconditionally.

3. Modify swagger's auto-generated server to tell spring not use "consumes" when HTTP GET.

4. Modify swagger's YAML spec to let "consumes" property apply to down level.

How do you think?

@jjqq2013
Copy link
Author

Think more and more, i found it seems more reasonable to do option 3(modify swagger's server code):

  ...
  @RequestMapping(value = "", 
    produces = { "application/mycom-v1+json" }, 
    consumes = { "application/mycom+json" },       //IS THIS REASONABLE ? Remove it so everything happy.
    method = RequestMethod.GET)
  public ... somFunc(....) {
  ...

But i am worried about server does not work in case of someone send JSON body in request of HTTP GET.

Any idea?

@ePaul
Copy link
Contributor

ePaul commented Feb 29, 2016

@QianJin2013 Your syntax error is resolved by adding a space after the -.

The following Yaml shows no errors in the online swagger-editor, and generates server code with proper @RequestMapping annotation:

swagger: '2.0'
info:
  version: "0.0.0"
  title: <enter your title>

produces:
  - application/mycom-v1+json

paths:
  /persons:
    post:
      consumes:
        - application/mycom-v1+json
      description: |
        Gets `Person`s related to given person.
      parameters:
        - in: body
          name: person
          schema:
            type: object
            title: Person
            properties:
              name:
                type: string
      responses:
        200:
          description: Successful response
          schema:
            title: ArrayOfPersons
            type: array
            items:
              title: Person
              type: object
              properties:
                name:
                  type: string

Generated PersonsApi.java (imports omitted):

@Controller
@RequestMapping(value = "/persons", produces = {APPLICATION_JSON_VALUE})
@Api(value = "/persons", description = "the persons API")
@javax.annotation.Generated(value = "class io.swagger.codegen.languages.SpringMVCServerCodegen", date = "2016-02-29T21:48:05.397Z")
public class PersonsApi {


  @ApiOperation(value = "", notes = "Gets `Person`s related to given person.", response = InlineResponse200.class, responseContainer = "List")
  @io.swagger.annotations.ApiResponses(value = { 
    @io.swagger.annotations.ApiResponse(code = 200, message = "Successful response") })
  @RequestMapping(value = "", 
    produces = { "application/mycom-v1+json" }, 
    consumes = { "application/mycom-v1+json" },
    method = RequestMethod.POST)
  public ResponseEntity<List<InlineResponse200>> personsPost(

@ApiParam(value = ""  ) @RequestBody Person person
)
      throws NotFoundException {
      // do some magic!
      return new ResponseEntity<List<InlineResponse200>>(HttpStatus.OK);
  }


}

@ePaul
Copy link
Contributor

ePaul commented Feb 29, 2016

I think your option 3 (ignoring the consumes for GET and other no-body methods, both for server and client generation) is also a good idea.

@jjqq2013
Copy link
Author

@ePaul I am glad to here this news. No wonder i saw no error when i first moved consumes property down.
Thank you.

So, i feel it's good to just move consumes property down without modifying any other files.

I think this issue can be closed.

@wing328 wing328 closed this as completed Mar 1, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants