How to add query parameters to a HTTP GET request by OkHttp? - java

I am using the latest okhttp version: okhttp-2.3.0.jar
How to add query parameters to GET request in okhttp in java ?
I found a related question about android, but no answer here!

For okhttp3:
private static final OkHttpClient client = new OkHttpClient().newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
public static void get(String url, Map<String,String>params, Callback responseCallback) {
HttpUrl.Builder httpBuilder = HttpUrl.parse(url).newBuilder();
if (params != null) {
for(Map.Entry<String, String> param : params.entrySet()) {
httpBuilder.addQueryParameter(param.getKey(),param.getValue());
}
}
Request request = new Request.Builder().url(httpBuilder.build()).build();
client.newCall(request).enqueue(responseCallback);
}

Here's my interceptor
private static class AuthInterceptor implements Interceptor {
private String mApiKey;
public AuthInterceptor(String apiKey) {
mApiKey = apiKey;
}
#Override
public Response intercept(Chain chain) throws IOException {
HttpUrl url = chain.request().url()
.newBuilder()
.addQueryParameter("api_key", mApiKey)
.build();
Request request = chain.request().newBuilder().url(url).build();
return chain.proceed(request);
}
}

I finally did my code, hope the following code can help you guys. I build the URL first using
HttpUrl httpUrl = new HttpUrl.Builder()
Then pass the URL to Request requesthttp hope it helps .
public class NetActions {
OkHttpClient client = new OkHttpClient();
public String getStudentById(String code) throws IOException, NullPointerException {
HttpUrl httpUrl = new HttpUrl.Builder()
.scheme("https")
.host("subdomain.apiweb.com")
.addPathSegment("api")
.addPathSegment("v1")
.addPathSegment("students")
.addPathSegment(code) // <- 8873 code passthru parameter on method
.addQueryParameter("auth_token", "71x23768234hgjwqguygqew")
// Each addPathSegment separated add a / symbol to the final url
// finally my Full URL is:
// https://subdomain.apiweb.com/api/v1/students/8873?auth_token=71x23768234hgjwqguygqew
.build();
System.out.println(httpUrl.toString());
Request requesthttp = new Request.Builder()
.addHeader("accept", "application/json")
.url(httpUrl) // <- Finally put httpUrl in here
.build();
Response response = client.newCall(requesthttp).execute();
return response.body().string();
}
}

As mentioned in the other answer, okhttp v2.4 offers new functionality that does make this possible.
See http://square.github.io/okhttp/2.x/okhttp/com/squareup/okhttp/HttpUrl.Builder.html#addQueryParameter-java.lang.String-java.lang.String-
This is not possible with the current version of okhttp, there is no method provided that will handle this for you.
The next best thing is building an url string or an URL object (found in java.net.URL) with the query included yourself, and pass that to the request builder of okhttp.
As you can see, the Request.Builder can take either a String or an URL.
Examples on how to build an url can be found at What is the idiomatic way to compose a URL or URI in Java?

As of right now (okhttp 2.4), HttpUrl.Builder now has methods addQueryParameter and addEncodedQueryParameter.

You can create a newBuilder from existing HttoUrl and add query parameters there. Sample interceptor code:
Request req = it.request()
return chain.proceed(
req.newBuilder()
.url(
req.url().newBuilder()
.addQueryParameter("v", "5.60")
.build());
.build());

Use HttpUrl class's functions:
//adds the pre-encoded query parameter to this URL's query string
addEncodedQueryParameter(String encodedName, String encodedValue)
//encodes the query parameter using UTF-8 and adds it to this URL's query string
addQueryParameter(String name, String value)
more detailed: https://stackoverflow.com/a/32146909/5247331

Related

Retrofit2 responseBodyConverter: I need request too

In a not so exotic situation I need access to the original request when I handle the response in the responseBodyConverter. For example, right now I need the original url -- e.g. I want to adjust relative urls in the response HTML. And perhaps I will need some request header, like a cookie.
But I don't know how to get the request data.
Here is what I have:
HttpLoggingInterceptor hlog = ses.http_logger(servername);
if (hlog != null) {
builder.addInterceptor(hlog);
}
OkHttpClient okhttpClient = builder
.build();
return new Retrofit.Builder()
.baseUrl(base_url)
.client(okhttpClient)
.addConverterFactory(
new Converter.Factory() {
#Override
public Converter<ResponseBody, ?> responseBodyConverter(final Type type, final Annotation[] annotations, final Retrofit retrofit) {
return responseBody -> {
BufferedSource buffer = Okio.buffer(responseBody.source());
String html = buffer.readUtf8();
System.out.println(html);
};
}
})
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build()
;
I found this solution https://github.com/square/retrofit/issues/2267/
But I can not understand what it does or whether it is too complex or any side effects.
Can I get access to the request data in Retrofit2 while processing the response in responseBodyConverter and how?
Edit: the above works as expected, but a simpler way is preferred.

Issues using Retrofit2 to call GitHub REST API to update existing file

I'm attempting to use Retrofit to call the GitHub API to update the contents of an existing file, but am getting 404s in my responses. For this question, I'm interested in updating this file. Here is the main code I wrote to try and achieve this:
GitHubUpdateFileRequest
public class GitHubUpdateFileRequest {
public String message = "Some commit message";
public String content = "Hello World!!";
public String sha = "shaRetrievedFromSuccessfulGETOperation";
public final Committer committer = new Committer();
private class Committer {
Author author = new Author();
private class Author {
final String name = "blakewilliams1";
final String email = "blake#blakewilliams.org";
}
}
}
**GitHubUpdateFileResponse **
public class GitHubUpdateFileResponse {
public GitHubUpdateFileResponse() {}
}
GitHubClient
public interface GitHubClient {
// Docs: https://docs.github.com/en/rest/reference/repos#get-repository-content
// WORKS FINE
#GET("/repos/blakewilliams1/blakewilliams1.github.io/contents/qr_config.json")
Call<GitHubFile> getConfigFile();
// https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents
// DOES NOT WORK
#PUT("/repos/blakewilliams1/blakewilliams1.github.io/contents/qr_config.json")
Call<GitHubUpdateFileResponse> updateConfigFile(#Body GitHubUpdateFileRequest request);
}
Main Logic
// Set up the Retrofit client and add an authorization interceptor
UserAuthInterceptor interceptor =
new UserAuthInterceptor("blake#blakewilliams.org", "myActualGitHubPassword");
OkHttpClient.Builder httpClient =
new OkHttpClient.Builder().addInterceptor(interceptor);
Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.client(httpClient.build()).build();
client = retrofit.create(GitHubClient.class);
// Now make the request and process the response
GitHubUpdateFileRequest request = new GitHubUpdateFileRequest();
client.updateConfigFile(request).enqueue(new Callback<GitHubUpdateFileResponse>() {
#Override
public void onResponse(Call<GitHubUpdateFileResponse> call, Response<GitHubUpdateFileResponse> response) {
int responseCode = response.code();
// More code on successful update
}
#Override
public void onFailure(Call<GitHubUpdateFileResponse> call, Throwable t) {
Log.e("MainActivity", "Unable to update file" + t.getLocalizedMessage());
}
});
What currently happens:
Currently, the success callback is triggered, but with a response code of 404 like so:
Response{protocol=http/1.1, code=404, message=Not Found, url=https://api.github.com/repos/blakewilliams1/blakewilliams1.github.io/contents/qr_config.json}
Has anyone else encountered this? I first thought it was a problem with including '/content/' in the URL but I do the same thing for reading the file contents request and it works fine (also uses same URL just a GET instead of PUT).
For anyone interested in doing this in the future, I figured out the solution.
I needed to revise the request object structure
Rather than using an authentication interceptor, I instead added an access token to the header. Here is where you can create access tokens for Github, you only need to grant it permissions to the 'repos' options for this use case to work.
This is what my updated request object looks like:
public class GitHubUpdateFileRequest {
public String message;
public String content;
public String sha;
public final Committer committer = new Committer();
public GitHubUpdateFileRequest(String unencodedContent, String message, String sha) {
this.message = message;
this.content = Base64.getEncoder().encodeToString(unencodedContent.getBytes());
this.sha = sha;
}
private static class Committer {
final String name = "yourGithubUsername";
final String email = "email#yourEmailAddressForTheUsername.com";
}
}
Then from my code, I would just say:
GitHubUpdateFileRequest updateRequest = new GitHubUpdateFileRequest("Hello World File Contents", "This is the title of the commit", shaOfExistingFile);
For using this reqest, I updated the Retrofit client implementation like so:
// https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents
#Headers({"Content-Type: application/vnd.github.v3+json"})
#PUT("/repos/yourUserName/yourRepository/subfolder/path/to/specific/file/theFile.txt")
Call<GitHubUpdateFileResponse> updateConfigFile(
#Header("Authorization") String authorization, #Body GitHubUpdateFileRequest request);
And I call that interface like this:
githubClient.updateConfigFile("token yourGeneratedGithubToken", request);
And yes, you do need the prefix "token ". You could hardcode that header into the interface, but I pass it in so that I can store it in locations outside of my version control's reach for security reasons.

Add AWS Signature Header to all rest assured requests

I'm trying to call an get api which is hosted in aws api gateway via rest-assured
I'm able to sign the request and make a call. But to sign the request, I need to pass the full url to AWS to generate the Authorization Header.
For Ex. If I'm going to access an an endpoint
https://my-aws-api.com/basepath/v1/request/123
I need to sign the request via AWSSigner which needs the full endpoint to do so.
My current approach
String baseURI="https://my-aws-api.com";
String basePath="basepath/v1";
String requestPath="request/123";
String endpoint=baseURI+"/"+basePath+"/"+requestPath;
Map<String,String> signedHeaders= aws4sign(endpoint,defaultHeaders);
given()
.log().ifValidationFails()
.headers(signedHeaders)
.when()
.get(endpoint)
.then()
.log().ifValidationFails()
.statusCode(200);
If I do that , then I cant use RestAssured's baseURI, basePath and path params
I want to access it like
RestAssured.baseURI="https://my-aws-api.com";
RestAssured.basePath="basepath/v1";
given()
.log().ifValidationFails()
.pathParam("reqID", "123")
.when()
.get("request/{reqID}")
.then()
.log().ifValidationFails()
.statusCode(200);
AwsSigner
public static Map<String, String> aws4Sign(String endpoint, Map<String, String> headers) throws URISyntaxException {
String serviceName = "execute-api";
AWS4Signer aws4Signer = new AWS4Signer();
aws4Signer.setRegionName(EU_WEST_1.getName());
aws4Signer.setServiceName(serviceName);
DefaultRequest defaultRequest = new DefaultRequest(serviceName);
URI uri = new URI(endpoint);
defaultRequest.setEndpoint(new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), "", "", ""));
defaultRequest.setHttpMethod(HttpMethodName.GET);
defaultRequest.setResourcePath(uri.getRawPath());
defaultRequest.setHeaders(headers);
aws4Signer.sign(defaultRequest, DefaultAWSCredentialsProviderChain.getInstance().getCredentials());
return defaultRequest.getHeaders();
}
So My question is there any way, I can intercept the RestAssured's request before it makes the call, so that I can get the fully generated end point and add the aws signed header to the call.
I am not familiar with this library but from briefly reading its documentation and Javadoc, you should be able to use a RequestFilter to inspect and alter a request before it is sent out.
Take a look at the Filter section of the user guide.
Thanks to #Ashaman.
The Filter Section is what I'm looking for
You can get the uri and other headers that were passed with requests from RequestSpec and then send it to the function to sign them and remove the old headers and put the new headers. Then forward the request
#BeforeAll
public void init() {
RestAssured.baseURI = "https://my-aws-api.com";
RestAssured.filters((requestSpec, responseSpec, ctx) -> {
Map<String, String> headers = requestSpec.getHeaders()
.asList()
.stream()
.collect(Collectors.toMap(Header::getName, Header::getValue));
Map<String, String> signedHeaders = aws4sign(requestSpec.getURI(), headers);
requestSpec.removeHeaders();
requestSpec.headers(signedHeaders);
return ctx.next(requestSpec, responseSpec);
});
}
And for the tests I can use the features of Rest Assured normally
given()
.log().ifValidationFails()
.pathParam("reqID", "123")
.when()
.get("request/{reqID}")
.then()
.log().ifValidationFails()
.statusCode(200);

What is the best practices for retry mechanism while using Retrofit?

Problem Statement:
I'm using Retrofit in my application for API calls. Currently I've 20+ Retrofit Interfaces, with different Callbacks. Currently when app receives INVALID_SESSION_ID in anyone of these Interfaces (say UpdateUserAPI), I've to get new ACCESS_TOKEN, by invoking AccessTokenAPI.
Approach Suggested:
When app receives INVALID_SESSION_ID in Callback in UpdateUserAPI, invoke AccessTokenAPI to get new ACCESS_TOKEN. Upon receiving new ACCESS_TOKEN, post the actual call (with initial parameters in UpdateUserAPI) with new ACCESS_TOKEN. But this requires to save parameters in the class which implements UpdateUserAPI. Also I need to retry getting ACCESS_TOKEN only once, which should be handled.
What is the best approach to implement above requirement?
Create your own TokenInterceptor
public class TokenInterceptor implements Interceptor
Then set it to your okktpclient
Interceptor tokenInterceptor = new TokenInterceptor(provideUserLoginDao(appDatabase));
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(tokenInterceptor)
.writeTimeout(50, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
Useful information in this post also : Refreshing OAuth token using Retrofit without modifying all calls
Create your own custom interceptor and check your token/session_id is valid or not. If your session_id is expired and then hit your updateUserAPI to get new id and set this id in header or where you want. Here is some code samples.
RefreshTokenInterceptor
public static class RInterceptor implements Interceptor {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
try {
if (response.code() == 410) {
Response r = null;
try {
r = makeTokenRefreshCall(request, chain);
} catch (JSONException e) {
e.printStackTrace();
}
return r;
}
} catch (Exception e) {
e.printStackTrace();
}
return response;
}
}
private static Response makeTokenRefreshCall(Request req, Interceptor.Chain chain) throws JSONException, IOException {
/* fetch refreshed token, some synchronous API call, whatever Because we are responsible to return new response */
refreshTokenSync();
Request newRequest;
newRequest = req.newBuilder().header("authorization", NEW_TOKEN)/*.post(req.body())*/.build();
return chain.proceed(newRequest);
}
RESTClient
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(50, TimeUnit.SECONDS)
.writeTimeout(55, TimeUnit.SECONDS)
.connectTimeout(50, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.addInterceptor(new NetworkInterceptor())
.build();

sending empty content with HttpClient

I am trying to connect to an API of another company.
from the doc there is ::
even with your GET request, you'll need to include the Java equivalent of
curl_setopt($ch, CURLOPT_POSTFIELDS, $content), and you can set $data equal
to an empty array.
$content in their example is an empty JSON array.
I am using org.apache.commons.httpclient.
i am not sure how to add post fields to a org.apache.commons.httpclient.methods.GetMethod or if it is even possible.
i tried faking with a Content-Length of 2 but the GET times out (probably looking for content that i am not providing. if i remove the content-length i get an invalid response from the api server)
HttpClient client = new HttpClient();
GetMethod method = new GetMethod("https://api.xxx.com/account/");
method.addRequestHeader("Content-Type", "application/json");
method.addRequestHeader("X-Public-Key", APKey);
method.addRequestHeader("X-Signed-Request-Hash", "xxx");
method.addRequestHeader("Content-Length", "2");
int statusCode = client.executeMethod(method);
I don't think GetMethod includes any means of attaching a request body, because a GET request isn't supposed to have a body. (But having a body isn't actually prohibited, either - see: HTTP GET with request body .)
You're trying to use documentation written with a different language and a different client library in mind, so you'll have to use trial and error a bit. It sounds like they expect a request with no body, and you already have that. There's no good reason why they'd require a "Content-Length" with GET, but if that's the case, try setting it to 0.
This is how i resolved this issue
Created this class
public class HttpGetWithEntity extends HttpEntityEnclosingRequestBase {
public HttpGetWithEntity() {
super();
}
public HttpGetWithEntity(URI uri) {
super();
setURI(uri);
}
public HttpGetWithEntity(String uri) {
super();
setURI(URI.create(uri));
}
#Override
public String getMethod() {
return HttpGet.METHOD_NAME;
}
}
Then the calling function looks like
public JSONObject get(JSONObject payload, String URL) throws Exception {
JSONArray jsonArray = new JSONArray();
CloseableHttpClient client = HttpClientBuilder.create().build();
HttpGetWithEntity myGet = new HttpGetWithEntity(WeeblyAPIHost+URL);
myGet.setEntity( new StringEntity("[]") );
myGet.setHeader("Content-Type", "application/json");
myGet.setHeader("X-Public-Key", APIKey);
HttpResponse response = client.execute(myGet);
JSONParser parser = new JSONParser();
Object obj = parser.parse( EntityUtils.toString(response.getEntity(), "UTF-8") ) ;
JSONObject jsonResponse = (JSONObject) obj;
return jsonResponse;
}

Categories

Resources