http get request with body

2019-01-29 03:54发布

问题:

i know you shouldn't send a HTTP GET Request with a body, but ceilometer web api forces me to do so. I'm developing a ceilometer scala client, so I need a scala/java way to make a get request with a body. So far I tried with beeClient (http://www.bigbeeconsultants.co.uk) and in plain Java using httpConnection but I get a 404 error. In curl I can achieve the result in this way:

curl -X GET -H "X-Auth-Token: ..long long token here.." 
-H "Content-Type: application/json" 
-d '{"q": [{"field": "resource", "op": "eq", "value": "gdfsf"}]}'
http://137.204.57.150:8777/v2/meters/

That's my scala code that uses java HttpURLConnection:

import java.io._
import java.net._
val token = "myToken"
val url = new URL("http://137.204.57.150:8777/v2/meters/")
val body = "{\"q\": [{\"field\": \"resource\", \"op\": \"eq\", \"value\": \"gdfsf\"}]}"
val bodyLenght = body.length.toString
val connection = url.openConnection().asInstanceOf[HttpURLConnection]
connection.setRequestMethod("GET")
connection.setRequestProperty("Content-Type", "application/json")
connection.setRequestProperty("Content-Length", bodyLength)
connection.setRequestProperty("Accept", "*/*")
connection.setRequestProperty("X-Auth-Token", token)
connection.setDoInput(true)
connection.setDoOutput(true)
//SEND REQUEST
val wr = new DataOutputStream(connection.getOutputStream)
wr.write(body.getBytes)
wr.flush
wr.close
if (connection.getResponseCode == 200) 
    println("ok")
else
    println("error")

What's the difference between my Java implementation and the curl command? I can't see any, I tried checking the header of curl calling it with the -v argument and that's what I get:

* Hostname was NOT found in DNS cache
*   Trying 137.204.57.150...
* Connected to 137.204.57.150 (137.204.57.150) port 8777 (#0)
> GET /v2/meters/ HTTP/1.1
> User-Agent: curl/7.37.1
> Host: 137.204.57.150:8777
> Accept: */*
> X-Auth-Token: ...Token....
> Content-Type: application/json
> Content-Length: 60
> 
* upload completely sent off: 60 out of 60 bytes
* HTTP 1.0, assume close after body

And then I get the response.

Thank you in advance

回答1:

I resolved the problem using jetty-client implementation, that lets build http requests in anyway you want. Here's the code (it's not immutable but it's not that bad in scala):

val httpClient = new HttpClient()
httpClient.setConnectTimeout(connectTimeout)
httpClient.setFollowRedirects(false)
httpClient.setStopTimeout(readTimeout)
httpClient.start()
val resp = httpClient.newRequest(uri).
      method(HttpMethod.GET).
      header("X-Auth-Token",s).
      send()

Look that i'm using the blocking API but jetty provides also a Java NIO API.



回答2:

You use HTTP PUT or POST request when sending request body for Celiometer API.

I checked the Ceilometer documentation and found that all requests with request body use HTTP PUT or POST methods. No GET method with request body. http://docs.openstack.org/developer/ceilometer/webapi/v2.html



回答3:

In general, the specification does not prohibit body on any type of http request (GET, DELETE etc), so you can do it if needed. However by convention this is atypical.

The problem you're having is that there are assumptions about what you can and can't do in the implementation of URLConnection you're using. In general, you'll be using a HttpUrlConnection (as you cast to), which will actually be implemented by your jvm. For example, here is a sun specific implementation.

If you look at this implementation, you will see it assumes that a GET request where you need the output stream is actually a POST.

If you want a GET with a body, you need to use a different connection method, for example a library like apache-http-client. You could start by looking at this question. There may be better scala alternatives for you to start with.



回答4:

I found a working plain java solution here, using apache's httpclient, httpcore, and commons-logging libs.

You need to create a class and extend HttpEntityEnclosingRequestBase, overriding the method name:

import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;

public class HttpGetWithEntity extends HttpEntityEnclosingRequestBase {
    public final static String METHOD_NAME = "GET";

    @Override
    public String getMethod() {
        return METHOD_NAME;
    }
}

Then you just use it like this:

HttpClient httpClient = HttpClientBuilder.create().build();
HttpGetWithEntity e = new HttpGetWithEntity();
e.setURI(new URI(yourURL))
e.setEntity(yourEntity);
HttpResponse response = httpclient.execute(e);

Hope it helps.



回答5:

After checking the documentation of Ceilometer and cURL I can suggest two things.

  1. Use URL parameters instead of JSON

As per the documentation you can use the URL parameters or JSON. You can modify your request as specified below to achieve the same thing with URL parameters rather than JSON.

URL("http://YOURHOST.COM:8777/v2/meters/?q.field=resource&q.op=eq&q.value=gdfsf")
  1. In case you have a specific reason not to use URL parameters for your JSON approach I guess encoding is what is missing in your request. Parameters are required to be sent in query parameters only rather than body content. For that I guess you need to try with below encoded data as shown below based on your request in the question.

    URL("http://YOURHOST.COM:8777/v2/meters/?q=%5B%7B%22field%22%3A%20%22resource%22%2C%20%22op%22%3A%20%22eq%22%2C%20%22value%22%3A%20%22gdfsf%22%7D%5D%7D")

Here q is the root query parameter name, without token I was not able to validate it.

Replace YOURHOST.COM with ip address for your server as it was showing problem to me even after putting them in code block and please let me know.



回答6:

you can try like this also

@RequestMapping(value = "/listcategories", method = RequestMethod.GET)

private ModelAndView getCategories() {
    ModelAndView modelAndView = new ModelAndView("list-of-categories");
    List<Category> categories = categoryService.getAllCategories();
    modelAndView.addObject("categories", categories);
    return modelAndView;
}