How to build URL with AsyncHttpClient? - java

AsyncHttpClient provides only a simple example where the target URL already prebuilt and represented as a string literal:
Future<Response> whenResponse = asyncHttpClient.prepareGet("http://www.example.com/").execute();
But what about more complex scenarios where you have to deal with REST endpoints like this, for example:
/repos/{owner}/{repo}/contributors
How can you specify the base URL in AsyncHttpClient and how you can write code to consume this type of REST services?
P.S. Feign provides a very convenient way to design your code around REST services your application is about to consume

This is what I am using. It has issues but it works for now.
public final class ApiHelper {
...
public static Uri UriGet(String baseUrl, String endpoint) {
//org.apache.http.client.utils
URIBuilder uriBuilder = null;
//java.net
URI uriTemp = null;
try {
uriBuilder = new URIBuilder(baseUrl);
uriTemp = uriBuilder.setPath(Paths.get(uriBuilder.getPath(), endpoint).toString())
.build()
.normalize();
} catch (URISyntaxException e) {
e.printStackTrace();
}
//org.asynchttpclient.uri.
Uri uri = Uri.create(uriTemp.toString());
return uri;
}
//usage:
String endpoint = "{owner}/{repo}/contributors".replace("{owner}", owner).replace("{repo}", repo);
Uri uri = ApiHelper.UriGet(Baseurl, endpoint);

Related

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.

How can I avoid escaping a WebTarget query parameter?

I want to turn off URL encoding for Jersey requests, or for certain parameters.
The server I'm talking to requires the format of example.com/path?query=foo:bar
With Jackson WebTarget,
final WebTarget target = ClientBuilder.newClient()
.target(url)
.queryParam("query", "{queryVal}")
.resolveTemplate("queryVal", "foo:bar");
Sadly this produces example.com/path?query=foo bar which is not accepted by the server.
I've searched a lot for this and the only promising avenue seems to be something to do with javax.ws.rs.core.Configuration, but I haven't gotten far with that yet.
I figured it out: The solution is to use a request filter as documented in Advanced Features of the Client API
final WebTarget target = ClientBuilder.newClient()
.target(url)
.queryParam("query", "{queryVal}")
.resolveTemplate("queryVal", "foo:bar")
.register(Filter.class)
and then we have
#Provider
public class Filter implements ClientRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(Filter.class);
#Override
public void filter(ClientRequestContext ctx) throws IOException {
try {
logger.debug("Before: {}", ctx.getUri());
//this is gonna get ugly
ctx.setUri(new URI(
URLDecoder.decode(ctx.getUri().toString(), StandardCharsets.UTF_8.toString())));
logger.debug("After: {}", ctx.getUri());
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
Output:
com.example.client.Filter : Before: http://example.com/path?query=foo%3Abar
com.example.client.Filter : After: http://example.com/path?query=foo:bar

How to consume a RestFull service using Android Studio

I have a API using ASP.NET Web API which return a json list and receive as well, on .NET Platform to make a POST or GET I use something like :
private string UrlLocalTest = "http://192.168.0.102:3000/api/";
public async Task<HttpResponseMessage> PostWeb(Model model)
{
HttpClient client = new HttpClient();
Uri url = new Uri(string.Concat(Url, "gone/POST"));
return await client.PostAsJsonAsync(url, model);
}
public async Task<HttpResponseMessage> GET(string name)
{
var client = new HttpClient();
Uri url = new Uri(string.Concat(UrlLocalTestTwo, "/GET" + name));
var response = await client.GetAsync(url);
return response;
}
And to return a token from my WebApi using oauth2 it goes like :
public async Task<HttpResponseMessage> Authenticate(string pEmail = null, string pPassword = null)
{
var Client = new HttpClient();
var _tokenArgs = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string,string>("username",pEmail),
new KeyValuePair<string, string>("password",pPassword),
new KeyValuePair<string, string>("grant_type","password")
});
Uri url = new Uri(UrlLocalTest);
return await Client.PostAsync(url, _tokenArgs);
}
How can I perform those methods and actions using Android ? I found something close to this, but I believe is more complex on Android.
Have a look at OkHttp. It provides easy access to HTTP-based services without too much abstraction. Why do you think it will be more complex on Android?
User volley. It is very easy to use and handle.
https://developer.android.com/training/volley/simple.html

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

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

Using absolute URLs with Retrofit

I have a HAL API that I'm working with and in many cases I need to send request (with different methods) to a URL that I get back from API. Meaning I don't want to hardcode the path of URL in my retrofit api interface but what I want is just sending a simple request using retrofit to that URL.
I'm using Volley now and I know that I can use OkHttp for this purpose but I was wondering if there is a nice way of doing such thing in Retrofit?
Recently Square has released the Retrofit v2.0.0 BETA and it has a built-in support for dynamic URLs. Even though the Library is in Beta, based on what Jake Wharton told us in DroidCon NYC 2015, all the apis are stable and will not change. I'm personally adding it to my production so its up to you.
You will find the following links useful if you decide to do the upgrade:
Jake Wharton presentation # DroidCon NYC 2015
A very good guide on the changes
In simple word, you can now use the api annotations (like #GET or #POST and others) without any path and then you pass in a #URL to your api method that the method will use to call.
----------------Retrofit 1.x
I figured out a nice way for doing this and would like to share it.
The trick is to use the dynamic URL as your End Point in the creation of RestAdapter and then have a empty path on your API interface.
Here is how I did it:
public RestAdapter getHostAdapter(String baseHost){
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(baseHost)
.setRequestInterceptor(requestInterceptor)
.build();
return restAdapter;
}
I build my restAdapter using this method and then I have this in my interface: (this will not work if your URL has query parameters added to it. See next answer for solution to that case)
public interface General {
#GET("/")
void getSomething(Callback<SomeObject> callback);
}
and finally using them like this:
getHostAdapter("YOUR_DYNAMIC_URL").create(General.class)
.getSomething(new Callback<SomeObject>(){
...
})
Hope it helps.
In case that your URL has query parameters on it, the above solution will not work since it will add the '/' at the end of your base URL. for example if your URL is
https://www.google.com/?q=test
then the above solution will try to send the request to
https://www.google.com/?q=test/
which will fail because of mall format.
What we can do is one extra step and parsing the url. By parsing I mean just taking out all URL parameters and sending them in a QueryMap.
Here is how:
We should have the same structure describe above with a little change to our interface
public interface General {
#GET("/")
void getSomething(#QueryMap Map<String,String> queryMap, Callback<SomeObject> callback);
}
I just added a QueryMap to the above interface and now we can use this parser method:
public static void getSomething(#NonNull String urlString, #NonNull Callback<SomeObject> callback){
Uri uri = Uri.parse(urlString);
Set<String> queryParameterNames = uri.getQueryParameterNames();
String host = uri.getHost();
HashMap<String,String> queryMap = new HashMap<>();
Iterator<String> iterator = queryParameterNames.iterator();
while(iterator.hasNext()){
String queryName = iterator.next();
String queryParameter = uri.getQueryParameter(queryName);
queryMap.put(queryName, queryParameter);
}
getHostAdapter(host)
.create(General.class)
.getSomething(queryMap, callback);
}
now you can call this method like this:
getSomething("https://www.google.com/?q=test");
Enjoy coding.
Note: QueryMap was added on Retrofit v1.4.0
I also need a path on my URL, so I did this:
#GET("/{path}")
void getMatcherUrl(#Path(value = "path", encode = false) String path, #QueryMap Map<String, String> queryMap, RestCallback<MatcherResponse> matcherResponse);
/**
* Need to create a custom method because i need to pass a absolute url to the retrofit client
*
* #param urlString
* #param matcherResponse
*/
public void getMatcherUrl(#NonNull String urlString, #NonNull RestCallback<MatcherResponse> matcherResponse) {
Uri uri = Uri.parse(urlString);
Set<String> queryParameterNames = uri.getQueryParameterNames();
String host = uri.getHost();
String path = (uri.getPath().startsWith("/")) ? uri.getPath().substring(1) : uri.getPath();
HashMap<String, String> queryMap = new HashMap<>();
Iterator<String> iterator = queryParameterNames.iterator();
while (iterator.hasNext()) {
String queryName = iterator.next();
String queryParameter = uri.getQueryParameter(queryName);
queryMap.put(queryName, queryParameter);
}
getApiCoreService(host)
.getMatcherUrl(path, queryMap, matcherResponse);
}
public ApiCoreService getApiCoreService(String host) {
if (StringUtils.isEmpty(host))
this.endpoint = new RestEndpoint(RemoteConfigurationManager.getInstance().getApiCore(), "ApiCore");
else
this.endpoint = new RestEndpoint(host, "ApiCore");
return apiCoreService;
}
Adding to above two answers, Here is a working class that makes use of Queryparam and fires the absolute URL
public class VideoClient {
private static final String TAG = "VideoCLient";
private final RestAdapter restAdapter;
private General apiService;
private String hostName;
private LinkedHashMap<String, String> queryMap;
private String Url_Path;
public VideoClient(String BASE_URL) {
Log.d(TAG,"Base url is "+BASE_URL);
hostName =getHostNameAndGenerateQueryMap(BASE_URL);
Gson gson = new GsonBuilder()
.create();
RequestInterceptor interceptor = new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {}
};
restAdapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setEndpoint("http://"+hostName)
.setClient(new OkClient())
.setConverter(new GsonConverter(gson))
.setRequestInterceptor(interceptor)
.build();
private String getHostNameAndGenerateQueryMap(String urlString) {
Uri uri = Uri.parse(urlString);
Url_Path = (uri.getPath().startsWith("/")) ? uri.getPath().substring(1) : uri.getPath();
Set<String> queryParameterNames = uri.getQueryParameterNames();
String host = uri.getHost();
queryMap = new LinkedHashMap<>();
Iterator<String> iterator = queryParameterNames.iterator();
while (iterator.hasNext()) {
String queryName = iterator.next();
String queryParameter = uri.getQueryParameter(queryName);
Log.d(TAG,"query name "+queryName +" query param "+queryParameter);
queryMap.put(queryName, queryParameter);
}
return host;
}
public interface General {
/*void getVideo(#Path("auth_token") String authId,
#Query("et") String systemTime,#Query("service_id") String serviceID,
#Query("protocol") String scheme,#Query("play_url") String url,
#Query("us") String us,Callback<String> callback);
*/
#GET("/{path}")
getVideo(#Path(value="path", encode=false)String path,#QueryMap LinkedHashMap<String, String> queryMap);
}
public void getVideoDetails() {
Log.i("firing", "getVideoApi");
Log.d(TAG, "firing " + Url_Path + " function");
restAdapter.create(General.class).getVideo(Url_Path,queryMap, new Callback<Object>() {
#Override
public void success( Object o, Response response) {
Log.d(TAG," Success Response is "+response );
}
#Override
public void failure(RetrofitError error) {
Log.d(TAG, "Failure " + "Internal Error" + error);
}
});
}

Categories

Resources