Related
I am working on an Android project and trying to get Digest Authentication to work with Retrofit. I'm kind of amazed Retrofit doesn't natively support it (or more accurately, that OkHttp doesn't support it), but no point complaining I suppose.
I cruised through quite a few threads here and it appears the right solution is to integrate the Apache HttpClient (which natively supports Digest Auth) with Retrofit. This requires wrapped the HttpClient with a retrofit.client.Client implementation. The retrofit incoming values have to be parsed and built into a new HttpClient response which is then sent back to Retrofit to be processed normally. Credit to Jason Tu and his example at: https://gist.github.com/nucleartide/24628083decb65a4562c
Issue is, it isn't working. I'm getting a 401 Unauthorized every time and it's not clear to me why. Here's my Client impl:
public class AuthClientRedirector implements Client {
private final CloseableHttpClient delegate;
public AuthClientRedirector(String user, String pass, String hostname, String scope) {
Credentials credentials = new UsernamePasswordCredentials(user, pass);
AuthScope authScope = new AuthScope(hostname, 443, scope);
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(authScope, credentials);
delegate = HttpClientBuilder.create()
.setDefaultCredentialsProvider(credentialsProvider)
.build();
}
#Override
public Response execute(Request request) {
//
// We're getting a Retrofit request, but we need to execute an Apache
// HttpUriRequest instead. Use the info in the Retrofit request to create
// an Apache HttpUriRequest.
//
String method = request.getMethod();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
if (request.getBody() != null) {
try {
request.getBody().writeTo(bos);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
String body = new String(bos.toByteArray());
HttpUriRequest wrappedRequest;
switch (method) {
case "GET":
wrappedRequest = new HttpGet(request.getUrl());
break;
case "POST":
wrappedRequest = new HttpPost(request.getUrl());
wrappedRequest.addHeader("Content-Type", "application/xml");
try {
((HttpPost) wrappedRequest).setEntity(new StringEntity(body));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
case "PUT":
wrappedRequest = new HttpPut(request.getUrl());
wrappedRequest.addHeader("Content-Type", "application/xml");
try {
((HttpPut) wrappedRequest).setEntity(new StringEntity(body));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
case "DELETE":
wrappedRequest = new HttpDelete(request.getUrl());
break;
default:
throw new AssertionError("HTTP operation not supported.");
}
CloseableHttpResponse apacheResponse = null;
try {
apacheResponse = delegate.execute(wrappedRequest);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(apacheResponse!=null){
// Perform the HTTP request.
CloseableHttpResponse response = null;
try {
response = delegate.execute(wrappedRequest);
// Return a Retrofit response.
List<Header> retrofitHeaders = toRetrofitHeaders(
response.getAllHeaders());
TypedByteArray responseBody;
if (response.getEntity() != null) {
responseBody = new TypedByteArray("",
toByteArray(response.getEntity()));
} else {
responseBody = new TypedByteArray("",
new byte[0]);
}
System.out.println("this is the response");
System.out.println(new String(responseBody.getBytes()));
return new retrofit.client.Response(request.getUrl(),
response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase(), retrofitHeaders,
responseBody);
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//failed to return a new retrofit Client
return null;
}
private List<Header> toRetrofitHeaders(org.apache.http.Header[] headers) {
List<Header> retrofitHeaders = new ArrayList<>();
for (org.apache.http.Header header : headers) {
retrofitHeaders.add(new Header(header.getName(), header.getValue()));
}
return retrofitHeaders;
}
private byte[] toByteArray(HttpEntity entity) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
entity.writeTo(bos);
return bos.toByteArray();
}
}
My retrofit configuration looks like this:
public final RestAdapter configureService(){
AuthClientRedirector digestAuthMgr = new AuthClientRedirector(username,password,"myhostname","public");
RestAdapter.Builder builder = new RestAdapter.Builder()
.setEndpoint("http://myhostname:8003/endpoint")
.setLogLevel(RestAdapter.LogLevel.FULL)
.setClient(digestAuthMgr);
return builder.build();
}
I am stumped why I'm consistently getting 401s back from the server. I've walked through the response building process and it looks clean to me, so I'm thinking I'm missing something fundamental. The credentials are good and I have verified them outside the app. Anyone walked this walk before?
You are using port number 443 for authentication.
AuthScope authScope = new AuthScope(hostname, 443, scope);
But, it seems that your real port number is 8003.
RestAdapter.Builder builder = new RestAdapter.Builder()
.setEndpoint("http://myhostname:8003/endpoint")
So, how about use port number 8003 for authentication like below?
AuthScope authScope = new AuthScope(hostname, 8003, scope);
I'm trying to call to my API sending a JSON to delete a product from my DB; however, it doesn't delete anything.
The response of the JSON is "true," and it doesn't give to me any error; even so, when I make a query on my DB, the product is still there.
I've created a class called HttpDeleteWithBody that looks like:
class HttpDeleteWithBody extends HttpEntityEnclosingRequestBase {
public static final String METHOD_NAME = "DELETE";
public String getMethod() { return METHOD_NAME; }
public HttpDeleteWithBody(final String uri) {
super();
setURI(URI.create(uri));
}
public HttpDeleteWithBody(final URI uri) {
super();
setURI(uri);
}
public HttpDeleteWithBody() { super(); }
}
And then on my doInBackGround of my Fragment, I do this:
boolean resul = true;
try {
JSONObject usuari = new JSONObject();
try {
usuari.put("idProducte", params[0]);
usuari.put("idusuari", params[1]);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
HttpEntity entity = new StringEntity(usuari.toString());
HttpClient httpClient = new DefaultHttpClient();
HttpDeleteWithBody httpDeleteWithBody = new HttpDeleteWithBody(getResources().getString(R.string.IPAPI) + "produsuaris/produsuari");
httpDeleteWithBody.setEntity(entity);
HttpResponse response = httpClient.execute(httpDeleteWithBody);
Log.d("Response ---------->", response.getStatusLine().toString());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} catch (Exception ex) {
Log.e("ServicioRest", "Error!", ex);
}
return resul;
Furthermore, I've tried to do this:
HttpDeleteWithBody delete = new HttpDeleteWithBody(getResources().getString(R.string.IPAPI) + "produsuaris/produsuari");
StringEntity se = new StringEntity(usuari.toString(), HTTP.UTF_8);
se.setContentType("application/json");
delete.setEntity(se);
however, it doesn't work... the log says:
D/Response ---------->﹕ HTTP/1.1 200 OK
This is how I call the method:
JSONObject deleteproduct = new JSONObject();
try {
deleteproduct.put("idProducte", String.valueOf(IDPROD));
deleteproduct.put("idusuari", String.valueOf(IDUSU));
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Log.i("Json test per afegir prod --> ", deleteproduct.toString());
TareaWSInsertar tarea = new TareaWSInsertar();
tarea.execute(String.valueOf(IDPROD), String.valueOf(IDUSU));
I've added on my Google Chrome a plug-in called "PostMan" and when I try to do this by this way, it's deleting correctly...
What I'm doing wrong?
EDIT
I tried to use cURL, and this is the result:
It is returning me false, when I put the same JSON as PostMan; nevertheless, if I put the same JSON on PostMan, it works fine.
EDIT 2
I implemented ion library and I did it like :
JSONObject usuari = new JSONObject();
try {
usuari.put("idProducte", params[0]);
usuari.put("idusuari", params[1]);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
String url = getResources().getString(R.string.IPAPI) + "produsuaris/produsuari";
Log.d("CURL", "curl -X DELETE -d '" + usuari.toString() + "' " + url);
Builders.Any.F builder = Ion.with(getActivity().getApplicationContext())
.load(HttpDelete.METHOD_NAME, url)
.setTimeout(15000).setStringBody(usuari.toString());
String response = builder.toString();
Log.d("TEST", "Req response -->" + response);
}
catch (Exception ex){
resul = false;
}
And it still returning that it's OK, and don't delete anything.
This appears to be a server side issue, to be sure of this, do the following:
1) Add Ion as an dependency in your grandle.
compile 'com.koushikdutta.ion:ion:+'
2) Use the following snippen to perform your request:
JSONObject jsonObject = new JSONObject();
try{
for(BasicNameValuePair aNameValue : getParameters()){
jsonObject.put(aNameValue.getName(), aNameValue.getValue());
Log.d("TEST","parameter "+aNameValue.getName()+": "+aNameValue.getValue());
}
jsonObject.put("time_zone", Util.timeZone());
Log.d("TEST","parameter time_zone:"+Util.timeZone());
}catch(Exception e){
//
}
Log.d("CURL", "curl -X DELETE -d '"+jsonObject.toString()+"' "+getUrl());
Builders.Any.F builder = Ion.with(getContext())
.load(HttpDelete.METHOD_NAME, getUrl())
.setTimeout(BuildConfig.HttpClientMaxTimeout).setStringBody(jsonObject.toString());
String response = builder.asString().get();
Util.checkThreadUiException();
Log.d("TEST","-->"+ response);
There's no much rocket science, this is the code that I used in an app, in that method I received the parameters to send as a json, as a BasicNameValuePair collection. You can change that and directly set your json. I'm 100% porcent sure that this request will fail, because this is a server side issue.
UPDATE
JSONObject usuari = new JSONObject();
try {
usuari.put("idProducte", params[0]);
usuari.put("idusuari", params[1]);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String url = getResources().getString(R.string.IPAPI) + "produsuaris/produsuari";
Log.d("CURL", "curl -X DELETE -d '"+usuari.toString()+"' "+url);
Builders.Any.F builder = Ion.with(getContext())
.load(HttpDelete.METHOD_NAME, url)
.setTimeout(BuildConfig.HttpClientMaxTimeout).setStringBody(usuari.toString());
String response = builder.asString().get();
Log.d("TEST","Req response -->"+ response)
UPDATE
Try this, perform this request through curl and let me know the result:
curl --http1.0 -X DELETE -d '{"idusuari":121,"idProducte":15}' 192.168.1.46/ServicioWebRest/api/produsuaris/produsuari
Doing this you're telling to curl to send the request through http 1.0, chunked responses are only supported by http 1.1, if there's an error in the chunk encoding, this should tell you.
Also take a look to this issue that I submitted to Ion long ago. I think that the problem that I was having that time, and your current problem are alike, maybe some of the tips there will help. Specially the part about the addHeader("Connection", "close").
Would look like this:
Builders.Any.F builder = Ion.with(getContext())
.addHeader("Connection", "close")
.load(HttpDelete.METHOD_NAME, getUrl())
.setTimeout(BuildConfig.HttpClientMaxTimeout).setStringBody(jsonObject.toString());
Finally I solved the problem, what I've done is change the HttpDelete method on my API, and instead of send a JSON, I send parameters (like a HttpGet) and now my code is like :
boolean resul = true;
HttpClient httpClient = new DefaultHttpClient();
String id____USER = params[0];
String id____PROD = params[1];
HttpDelete del =
new HttpDelete(getResources().getString(R.string.IPAPI) + "produsuaris/produsuari?idProd=" + Integer.parseInt(id____PROD)+"&idUs="+Integer.parseInt(id____USER));
del.setHeader("content-type", "application/json");
try
{
HttpResponse resp = httpClient.execute(del);
String respStr = EntityUtils.toString(resp.getEntity());
if(!respStr.equals("true"))
resul = false;
}
catch(Exception ex)
{
Log.e("ServicioRest","Error!", ex);
resul = false;
}
return resul;
This is how I call this AsyncMethod
TareaWSInsertar tarea = new TareaWSInsertar();
tarea.execute(String.valueOf(IDUSU),String.valueOf(IDPROD));
This work to me, I know it's not the best solution, but I've no much time, also I tried three solutions and noone didn't work.
Feel free to post a correct answer if you know what I was doing wrong.
I want to check progress of uploading file by HttpUrlConnection. How I can do this? I've tried to calculate bytes when writing data in OutputStream but it's wrong, cause real uploading happens only when I call conn.getInputStream(), so I need somehow to check inputStream. Here is my code:
public static void uploadMovie(final HashMap<String, String> dataSource, final OnLoadFinishedListener finishedListener, final ProgressListener progressListener) {
if (finishedListener != null) {
new Thread(new Runnable() {
public void run() {
try {
String boundary = getMD5(dataSource.size()+String.valueOf(System.currentTimeMillis()));
MultipartEntityBuilder multipartEntity = MultipartEntityBuilder.create();
multipartEntity.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
multipartEntity.setCharset(Charset.forName("UTF-8"));
for (String key : dataSource.keySet()) {
if (key.equals(MoviesFragmentAdd.USERFILE)) {
FileBody userFile = new FileBody(new File(dataSource.get(key)));
multipartEntity.addPart(key, userFile);
continue;
}
multipartEntity.addPart(key, new StringBody(dataSource.get(key),ContentType.APPLICATION_JSON));
}
HttpEntity entity = multipartEntity.build();
HttpURLConnection conn = (HttpsURLConnection) new URL(URL_API + "/video/addForm/").openConnection();
conn.setUseCaches(false);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Accept-Charset", "UTF-8");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Cache-Control", "no-cache");
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
conn.setRequestProperty("Content-length", entity.getContentLength() + "");
conn.setRequestProperty(entity.getContentType().getName(),entity.getContentType().getValue());
OutputStream os = conn.getOutputStream();
entity.writeTo(os);
os.close();
//Real upload starting here -->>
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
//<<--
JsonObject request = (JsonObject) gparser.parse(in.readLine());
if (!request.get("error").getAsBoolean()) {
//do something
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
Because you have to deal with upload, I'd suppose most time is taken when doing entity.writeTo(os);. Maybe the first contact to the server takes some time as well (DNS resolution, SSL-handshake, ...). The markers you set for "the real upload" are not correct IMO.
Now it depends on your Multipart-library, whether you can intercept writeTo. If it is clever and resource-efficient, it's iterating over the parts and streams the content one-by-one to the output stream. If not, and the .build() operation is creating a big fat byte[], then you could take this array, stream it in chunks to the server and tell your user how many percent of the upload is already done.
From a resource perspective I'd prefer not really knowing what happens. But if feedback is that important and if the movies are only a few megabytes in size, you could stream the Multipart-Entity first to a ByteArrayOutputStream and then write little chunks of the created byte-array to the server while notifying your user about progress. The following code is not validated or tested (you can see it as pseudo-code):
ByteArrayOutputStream baos = new ByteArrayOutputStream();
entity.writeTo(baos);
baos.close();
byte[] payload = baos.toByteArray();
baos = null;
OutputStream os = conn.getOutputStream();
int totalSize = payload.length;
int bytesTransferred = 0;
int chunkSize = 2000;
while (bytesTransferred < totalSize) {
int nextChunkSize = totalSize - bytesTransferred;
if (nextChunkSize > chunkSize) {
nextChunkSize = chunkSize;
}
os.write(payload, bytesTransferred, nextChunkSize); // TODO check outcome!
bytesTransferred += nextChunkSize;
// Here you can call the method which updates progress
// be sure to wrap it so UI-updates are done on the main thread!
updateProgressInfo(100 * bytesTransferred / totalSize);
}
os.close();
A more elegant way would be to write an intercepting OutputStream which registers progress and delegates the real write-operations to the underlaying "real" OutputStream.
Edit
#whizzzkey wrote:
I've re-checked it many times - entity.writeTo(os) DOESN'T do a real upload, it does conn.getResponseCode() or conn.getInputStream()
Now it's clear. HttpURLConnection is buffering your upload data, because it doesn't know the content-length. You've set the header 'Content-length', but oviously this is ignored by HUC. You have to call
conn.setFixedLengthStreamingMode(entity.getContentLength());
Then you should better remove the call to conn.setRequestProperty("Content-length", entity.getContentLength() + "");
In this case, HUC can write the headers and entity.writeTo(os) can really stream the data to the server. Otherwise the buffered data is sent when HUC knows how many bytes will be transferred. So in fact, getInputStream() tells HUC that you're finished, but before really reading the response, all the collected data has to be sent to the server.
I wouldn't recommend changing your code, but for those of you who don't know the exact size of the transferred data (in bytes, not characters!!), you can tell HUC that it should transfer the data in chunks without setting the exact content-length:
conn.setChunkedStreamingMode(-1); // use default chunk size
Right this code in your activity...
public class PublishPostToServer extends AsyncTask implements
ProgressListenerForPost {
public Context pContext;
public long totalSize;
private String response;
public PublishPostToServer(Context context) {
pContext = context;
}
protected void onPreExecute() {
showProgressDialog();
}
#Override
protected Boolean doInBackground(Void... params) {
boolean success = true;
try {
response = NetworkAdaptor.getInstance()
.upLoadMultipartImageToServer(
"",
"",
"", this, this); // Add file path, Authkey, caption
} catch (Exception e) {
success = false;
}
return success;
}
#Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
//validateResponse(result, response);
}
#Override
protected void onProgressUpdate(Integer... values) {
try {
if (mProgressDialog != null) {
mProgressDialog.setProgress(values[0]);
}
} catch (Exception exception) {
}
}
#Override
public void transferred(long num) {
publishProgress((int) ((num / (float) totalSize) * 100));
}
}
private void showProgressDialog() {
try {
String dialogMsg = "Uploading Image...";
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage(dialogMsg);
mProgressDialog.setIndeterminate(false);
mProgressDialog.setMax(100);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mProgressDialog.setCancelable(false);
mProgressDialog.show();
} catch (Exception exception) {
}
}
Now, Make a NetworkAdapter Class
public String upLoadMultipartImageToServer(String sourceFileUri,
String auth_key, String caption, ProgressListenerForPost listiner,
PublishPostToServer asyncListiner) {
String upLoadServerUri = "" + "upload_image";
HttpPost httppost = new HttpPost(upLoadServerUri);
File file = new File(sourceFileUri);
if (file.exists()) {
FileBody filebodyVideo = new FileBody(file);
CustomMultiPartEntity multipartEntity = new CustomMultiPartEntity(
HttpMultipartMode.BROWSER_COMPATIBLE, listiner);
try {
multipartEntity.addPart("auth_key", new StringBody(auth_key));
multipartEntity.addPart("caption", new StringBody(caption));
multipartEntity.addPart("image", filebodyVideo);
asyncListiner.totalSize = multipartEntity.getContentLength();
httppost.setEntity(multipartEntity);
}
catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
DefaultHttpClient mHttpClient = new DefaultHttpClient();
String response = "";
try {
response = mHttpClient.execute(httppost,
new MovieUploadResponseHandler());
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return response;
} else {
return null;
}
}
#SuppressWarnings("rawtypes")
private class MovieUploadResponseHandler implements ResponseHandler {
#Override
public Object handleResponse(HttpResponse response)
throws ClientProtocolException, IOException {
HttpEntity r_entity = response.getEntity();
String responseString = EntityUtils.toString(r_entity);
// DebugHelper.printData("UPLOAD", responseString);
return responseString;
}
}
public static boolean isValidResponse(String resultData) {
try {
} catch (Exception exception) {
//DebugHelper.printException(exception);
}
return true;
}
public String upLoadVideoToServer(String currentFilePath, String string,
PublishPostToServer publishPostToServer,
PublishPostToServer publishPostToServer2) {
// TODO Auto-generated method stub
return null;
}
One assumed using the Tumblr API to upload images would be easy. It isn't. (EDIT It is now, see Edit 2 at the end of this entry)
My app is supposed to upload an image to tumblr. I would prefer doing that from a service but for now I use an activity that closes itself as soon as its done uploading. In OnCreate() the user is authenticated:
consumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
// It uses this signature by default
// consumer.setMessageSigner(new HmacSha1MessageSigner());
provider = new CommonsHttpOAuthProvider(REQUEST_TOKEN_URL,ACCESS_TOKEN_URL,AUTH_URL);
String authUrl;
try
{
authUrl = provider.retrieveRequestToken(consumer, CALLBACK_URL);
Log.d(TAG, "Auth url:" + authUrl);
startActivity(new Intent("android.intent.action.VIEW", Uri.parse(authUrl)));
}
This opens a browser activity where the user can add username and passoword and then the app returns to the activity (this is also why I have to use an activity, I don't know how to do this from a service)
Returning from the browser the data is extracted:
Uri uri = context.getIntent().getData();
if (uri != null && uri.toString().startsWith(CALLBACK_URL))
{
Log.d(TAG, "uri!=null");
String verifier = uri.getQueryParameter("oauth_verifier");
Log.d(TAG, "verifier"+verifier);
try
{
provider.setOAuth10a(true);
provider.retrieveAccessToken(consumer, verifier);
Log.d(TAG, "try");
}
catch (Exception e)
{
Log.e(TAG, e.toString());
e.printStackTrace();
}
OAUTH_TOKEN = consumer.getToken();
OAUTH_SECRET = consumer.getTokenSecret();
Most of these two snippets I got from here and they work well.
With these tokens I can now try putting data on tumblr. When I try to add Text this works fine using this method:
private void createText()
{
if(!OAUTH_TOKEN.equals(""))
{
HttpContext context = new BasicHttpContext();
HttpPost request = new HttpPost("http://api.tumblr.com/v2/blog/" + blogname + ".tumblr.com/post");
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair("type", "text"));
nameValuePairs.add(new BasicNameValuePair("body", "this is just a test"));
try
{
request.setEntity(new UrlEncodedFormEntity(nameValuePairs));
}
catch (UnsupportedEncodingException e1)
{
Log.e(TAG, e1.toString());
e1.printStackTrace();
}
if (consumer == null)
{
consumer = new CommonsHttpOAuthConsumer(OAuthConstants.TUMBR_CONSUMERKEY, OAuthConstants.TUMBR_SECRETKEY);
}
if (OAUTH_TOKEN == null || OAUTH_SECRET == null)
{
Log.e(TAG, "Not logged in error");
}
consumer.setTokenWithSecret(OAUTH_TOKEN, OAUTH_SECRET);
try
{
consumer.sign(request);
}
catch (OAuthMessageSignerException e)
{
}
catch (OAuthExpectationFailedException e)
{
}
catch (OAuthCommunicationException e)
{
}
HttpClient client = new DefaultHttpClient();
//finally execute this request
try
{
HttpResponse response = client.execute(request, context);
HttpEntity responseEntity = response.getEntity();
if (responseEntity != null)
{
Log.d(TAG, "responseEntety!=null");
try
{
Log.d(TAG, EntityUtils.toString(responseEntity));
}
catch (ParseException e)
{
e.printStackTrace();
Log.e(TAG, e.toString());
}
catch (IOException e)
{
e.printStackTrace();
Log.e(TAG, e.toString());
} // gives me {"meta":{"status":401,"msg":"Not Authorized"},"response":[]} when I try to upload a photo
}
else
{
Log.d(TAG, "responseEntety==null");
}
}
catch (ClientProtocolException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
PostToTumblr.this.finish();
}
As you can see here http://www.tumblr.com/blog/snapnowandroid (at least as of this time) the text "this is just a test" is posted.
However, when I try to post images, it gets strange. Now I have checked around and apparently this is a well known issue with the tumblr API, which has excessively been discussed here and some have solved it in other programming languages (for example here) but I have been unable to repeat those successes.
The method (in its entirety below) has the exact same structure to the above method (that works), the nameValuePairs are just different
The method is given a Bitmap variable called photo:
private void uploadToTumblr(Bitmap photo)
This bitmap is converted into an array:
ByteArrayOutputStream stream = new ByteArrayOutputStream();
photo.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] bytes = stream.toByteArray();
The nameValuePairs are filled as follows:
nameValuePairs.add(new BasicNameValuePair(URLEncoder.encode("type", enc), URLEncoder.encode("photo", enc)));
nameValuePairs.add(new BasicNameValuePair(URLEncoder.encode("caption", enc), URLEncoder.encode(text, enc)));
nameValuePairs.add(new BasicNameValuePair("data", Base64.encodeToString(bytes, Base64.URL_SAFE)));
The result is a {"meta":{"status":400,"msg":"Bad Request"},"response":{"errors":["Error uploading photo."]}} from the tumblr api.
I have tries encoding the picture differently as discribed in this article but without any changes.
//http://www.coderanch.com/t/526487/java/java/Java-Byte-Hex-String
final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
char[] hexChars = new char[bytes.length * 3];
int v;
for ( int j = 0; j < bytes.length; j++ )
{
v = bytes[j] & 0xFF;
hexChars[j * 3] = '%';
hexChars[j * 3 + 1] = hexArray[v >>> 4];
hexChars[j * 3 + 2] = hexArray[v & 0x0F];
}
String s = new String(hexChars);
s = URLEncoder.encode(s, enc);
nameValuePairs.add(new BasicNameValuePair(URLEncoder.encode("data", enc), s));
Here the entire method (without the hex encoding):
private void uploadToTumblr(Bitmap photo)
{
if(!OAUTH_TOKEN.equals(""))
{
ByteArrayOutputStream stream = new ByteArrayOutputStream();
photo.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] bytes = stream.toByteArray();
String text ="SNAP";
HttpContext context = new BasicHttpContext();
HttpPost request = new HttpPost("http://api.tumblr.com/v2/blog/" + blogname + ".tumblr.com/post");
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
String enc = "UTF-8";
try
{
nameValuePairs.add(new BasicNameValuePair(URLEncoder.encode("type", enc), URLEncoder.encode("photo", enc)));
nameValuePairs.add(new BasicNameValuePair(URLEncoder.encode("caption", enc), URLEncoder.encode(text, enc)));
nameValuePairs.add(new BasicNameValuePair("data", Base64.encodeToString(bytes, Base64.URL_SAFE)));
}
catch (UnsupportedEncodingException e2)
{
Log.e(TAG, e2.toString());
e2.printStackTrace();
}
try
{
request.setEntity(new UrlEncodedFormEntity(nameValuePairs));
}
catch (UnsupportedEncodingException e1)
{
Log.e(TAG, e1.toString());
e1.printStackTrace();
}
if (consumer == null)
{
consumer = new CommonsHttpOAuthConsumer(OAuthConstants.TUMBR_CONSUMERKEY, OAuthConstants.TUMBR_SECRETKEY);
}
if (OAUTH_TOKEN == null || OAUTH_SECRET == null)
{
//throw new LoginErrorException(LoginErrorException.NOT_LOGGED_IN);
Log.e(TAG, "Not logged in error");
}
consumer.setTokenWithSecret(OAUTH_TOKEN, OAUTH_SECRET);
try
{
consumer.sign(request);
}
catch (OAuthMessageSignerException e)
{
}
catch (OAuthExpectationFailedException e)
{
}
catch (OAuthCommunicationException e)
{
}
HttpClient client = new DefaultHttpClient();
//finally execute this request
try
{
HttpResponse response = client.execute(request, context);
HttpEntity responseEntity = response.getEntity();
if (responseEntity != null)
{
Log.d(TAG, "responseEntety!=null");
try
{
Log.d(TAG, EntityUtils.toString(responseEntity));
}
catch (ParseException e)
{
e.printStackTrace();
Log.e(TAG, e.toString());
}
catch (IOException e)
{
e.printStackTrace();
Log.e(TAG, e.toString());
}
}
else
{
Log.d(TAG, "responseEntety==null");
}
}
catch (ClientProtocolException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else
{
Log.d(TAG, "upload imposble... Toklen not set");
}
PostToTumblr.this.finish();
}
Now, while there are several things I am unhappy with (for example that this is done using an activity instead of a service) the big issue here is clearly the problem of uploading images. I am by no means the first to have this problem, so has anyone been able to get this done in java?
Edit 1
Have not made any progress with the problem at hand but created a workaround that might be nice for people who have the same issue. Tumblr offers posting via mail and you can programm android to send emails in the background as shown here. This works very well but you need to ask users to provide their mail account data and the Tumblr-mail Adress to post.
Edit 2
Years have pased and using email is no longer the easy way to do it. With jumblr there is finally a good API for Java that will work on android. OAuth-Authentication is no fun (it never is) but once you get past this, its fantastic.
Now, technically the question of how to do the authentication does not belong here but It's my overly long question, so I'll just paste some code here and if it's not interesting to you just skip it.
This uses a jar called jumblr-0.0.10-jar-with-dependencies.jar
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import com.tumblr.jumblr.JumblrClient;
import com.tumblr.jumblr.request.RequestBuilder;
import com.tumblr.jumblr.types.Blog;
import com.tumblr.jumblr.types.User;
import org.scribe.builder.ServiceBuilder;
import org.scribe.builder.api.TumblrApi;
import org.scribe.model.Token;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;
import java.io.File;
public class Tumblr
{
private static final String PROTECTED_RESOURCE_URL = "http://api.tumblr.com/v2/user/info";
static OAuthService service;
static Token requestToken=null;
public static void share(final Activity ctx, File file)
{
Thread tt = new Thread(new Runnable()
{
#Override
public void run()
{
JumblrClient client = new JumblrClient(Tumblr_Constants.CONSUMER_KEY, Tumblr_Constants.CONSUMER_SECRET);
RequestBuilder requestBuilder = client.getRequestBuilder();
requestBuilder.setConsumer(Tumblr_Constants.CONSUMER_KEY, Tumblr_Constants.CONSUMER_SECRET);
SharedPreferences settings = ctx.getSharedPreferences("TumblrData", 0);
String oauthToken=settings.getString("OauthToken", "");
String oauthTokenSecret=settings.getString("OauthSecret", "");
if(oauthToken.equals("") || oauthTokenSecret.equals(""))
{
authenticate(ctx);
while(WebViewFragment.verifier.equals(""))
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String v = WebViewFragment.verifier;
Token accessToken = authenticatefurther(v);
SharedPreferences.Editor edit = settings.edit();
edit.putString("OauthToken", accessToken.getToken());
edit.putString("OauthSecret", accessToken.getSecret());
edit.commit();
oauthToken=settings.getString("OauthToken", "");
oauthTokenSecret=settings.getString("OauthSecret", "");
}
if(!oauthToken.equals("") && !oauthTokenSecret.equals(""))
{
client.setToken(oauthToken, oauthTokenSecret);
User user = client.user();
System.out.println(user.getName());
for (Blog blog : user.getBlogs()) {
Log.d("TUMBLR", blog.getTitle());
}
}
}
});
tt.start();
}
private static void authenticate(Context ctx) {
service = new ServiceBuilder()
.provider( TumblrApi.class )
.apiKey(Tumblr_Constants.CONSUMER_KEY)
.apiSecret(Tumblr_Constants.CONSUMER_SECRET)
.callback("snapnao://snapnao.de/ok") // OOB forbidden. We need an url and the better is on the tumblr website !
.build();
Log.d("TUMBLR", "=== Tumblr's OAuth Workflow ===" );
System.out.println();
// Obtain the Request Token
Log.d("TUMBLR", "Fetching the Request Token...");
requestToken = service.getRequestToken();
Log.d("TUMBLR", "Got the Request Token!");
Log.d("TUMBLR", "");
Log.d("TUMBLR", "Now go and authorize Scribe here:" );
Log.d("TUMBLR", service.getAuthorizationUrl( requestToken ) );
String url = service.getAuthorizationUrl(requestToken);
Intent i = new Intent(ctx, WebViewFragment.class);
i.putExtra("url", url);
ctx.startActivity(i);
}
private static Token authenticatefurther(String v)
{
Token accessToken = null;
Log.d("TUMBLR", "And paste the verifier here");
Log.d("TUMBLR", ">>");
Verifier verifier = new Verifier( v);
Log.d("TUMBLR", "");
// Trade the Request Token and Verfier for the Access Token
Log.d("TUMBLR", "Trading the Request Token for an Access Token...");
accessToken = service.getAccessToken( requestToken ,
verifier );
Log.d("TUMBLR", "Got the Access Token!");
Log.d("TUMBLR", "(if your curious it looks like this: " + accessToken + " )");
Log.d("TUMBLR", "");
return accessToken;
}
}
The WebViewFragement looks like this:
import android.app.Activity;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Bundle;
import android.util.Log;
import android.webkit.SslErrorHandler;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class WebViewFragment extends Activity
{
public static String verifier="";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.webviewfragment);
String url = getIntent().getStringExtra("url");
Log.d("TUMBLR", "webview-> "+url);
WebView view = (WebView) findViewById(R.id.webView);
view.setWebViewClient(
new SSLTolerentWebViewClient()
);
view.getSettings().setJavaScriptEnabled(true);
view.loadUrl(url);
}
// SSL Error Tolerant Web View Client
private class SSLTolerentWebViewClient extends WebViewClient {
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed(); // Ignore SSL certificate errors
}
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
Log.d("TUMBLR", "+++++"+url);
if(url.contains("oauth_verifier="))
{
String[] x = url.split("oauth_verifier=");
verifier=x[1].replace("#_=_", "");
WebViewFragment.this.finish();
}
}
}
}
Why don't you use Jumblr the official Java client for Tumblr.
Regards.
You can easily do this using jumblr - Tumblr java client
JumblrClient client = new JumblrClient(Constant.CONSUMER_KEY,Constant.CONSUMER_SECRET);
client.setToken(preferences.getString("token",null), preferences.getString("token_secret", null));
PhotoPost pp = client.newPost(client.user().getBlogs().get(0).getName(),PhotoPost.class);
pp.setCaption(caption);
// pp.setLinkUrl(link);
// pp.setSource(mImage); // String URL
pp.setPhoto(new Photo(imgFile));
pp.save();
This worked for me...
nameValuePairs.add(new BasicNameValuePair(URLEncoder
.encode("type", "UTF-8"),
URLEncoder.encode("photo", "UTF-8")));
Log.e("Tumblr", "Image shareing file path" + filePath);
nameValuePairs.add(new BasicNameValuePair("caption", caption));
nameValuePairs.add(new BasicNameValuePair("source", filePath));`
where filePath is http url.
I have use multipart
public class VideoUploader extends AsyncTask {
ProgressDialog progressDialog;
#Override
protected void onPreExecute() {
// TODO Auto-generated method stub
progressDialog = ProgressDialog.show(RecordingActivity.this, "",
"Uploading video.. ");
super.onPreExecute();
}
#Override
protected JSONObject doInBackground(String... params) {
JSONObject jsonObject = null;
StringBuilder builder = new StringBuilder();
try {
String url = UrlConst.VIDEO_URL;
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
FileBody filebodyVideo = new FileBody(new File(params[0]));
StringBody title = new StringBody("uploadedfile: " + params[0]);
StringBody description = new StringBody(
"This is a video of the agent");
// StringBody code = new StringBody(realtorCodeStr);
MultipartEntity reqEntity = new MultipartEntity();
reqEntity.addPart("uploadedfile", filebodyVideo);
reqEntity.addPart("title", title);
reqEntity.addPart("description", description);
// reqEntity.adddPart("code", code);
httppost.setEntity(reqEntity);
// DEBUG
System.out.println("executing request "
+ httppost.getRequestLine());
HttpResponse response = httpclient.execute(httppost);
HttpEntity resEntity = response.getEntity();
// DEBUG
StatusLine status = response.getStatusLine();
int statusCode = status.getStatusCode();
System.out.println(response.getStatusLine());
if (resEntity != null) {
System.out.println(EntityUtils.toString(resEntity));
} // end if
if (resEntity != null) {
resEntity.consumeContent();
} // end if
if (statusCode == 200) {
InputStream content = resEntity.getContent();
BufferedReader reader = new BufferedReader(
new InputStreamReader(content));
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
jsonObject = new JSONObject(builder.toString());
return jsonObject;
} else {
Log.e(LoginActivity.class.toString(),
"Failed to download file");
}
httpclient.getConnectionManager().shutdown();
} catch (Exception e) {
// TODO: handle exception
}
return null;
}
#Override
protected void onPostExecute(JSONObject result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
progressDialog.dismiss();
if (result != null) {
try {
JSONObject jsonObject = result
.getJSONObject(ParsingTagConst.COMMANDRESULT);
String strSuccess = jsonObject
.getString(ParsingTagConst.SUCCESS);
String responseString = jsonObject
.getString(ParsingTagConst.RESPONSE_STRING);
Toast.makeText(RecordingActivity.this, "" + responseString,
Toast.LENGTH_LONG).show();
if (strSuccess.equals("1")) {
// get here your response
}
} catch (Exception e) {
// TODO: handle exception
}
}
}
}
enter code here
I have done using following method. you can try this.
//paramString="text you want to put in caption"
private void postPhotoTumblr(String uploadedImagePhotoUrl, String paramString)
{
CommonsHttpOAuthConsumer localCommonsHttpOAuthConsumer = getTumblrConsumer();
String str1 = "logged in username";
String encodedImage = uploadedImagePhotoUrl;
DefaultHttpClient localDefaultHttpClient = new DefaultHttpClient();
HttpPost localHttpPost = new HttpPost("http://api.tumblr.com/v2/blog/" + str1 + ".tumblr.com/post");
try
{
ArrayList localArrayList = new ArrayList();
localArrayList.add(new BasicNameValuePair("type", "photo"));
BasicNameValuePair localBasicNameValuePair = new BasicNameValuePair("caption", paramString);
localArrayList.add(localBasicNameValuePair);
localArrayList.add(new BasicNameValuePair("data",encodedImage));
UrlEncodedFormEntity localUrlEncodedFormEntity = new UrlEncodedFormEntity(localArrayList);
localHttpPost.setEntity(localUrlEncodedFormEntity);
localCommonsHttpOAuthConsumer.sign(localHttpPost);
InputStream localInputStream = localDefaultHttpClient.execute(localHttpPost).getEntity().getContent();
InputStreamReader localInputStreamReader = new InputStreamReader(localInputStream);
BufferedReader localBufferedReader = new BufferedReader(localInputStreamReader);
StringBuilder localStringBuilder = new StringBuilder();
while (true)
{
String str2 = localBufferedReader.readLine();
if (str2 == null)
{
Log.i("DATA post resp", localStringBuilder.toString());
break;
}
localStringBuilder.append(str2);
}
}
catch (ClientProtocolException localClientProtocolException)
{
localClientProtocolException.printStackTrace();
}
catch (IOException localIOException)
{
localIOException.printStackTrace();
}
catch (OAuthMessageSignerException localOAuthMessageSignerException)
{
localOAuthMessageSignerException.printStackTrace();
}
catch (OAuthExpectationFailedException localOAuthExpectationFailedException)
{
localOAuthExpectationFailedException.printStackTrace();
}
catch (OAuthCommunicationException localOAuthCommunicationException)
{
localOAuthCommunicationException.printStackTrace();
}
}
EDIT : First Upload image to Web Server then get Url and try to Post with uploaded Url or File path. it will work fine sure... :)
I am working with Android http stuff to register/unregister to the server. I have a DELETE request to use HttpDelete. I am getting Http401 'Bad request' error when I try to call it. I cannot why it is happening. Please help me.
Here is my code:
HttpUtils.java
private BasicHttpParams mParams;
private UsernamePasswordCredentials mCredentials = null;
private ResponseHandler mResponseHandler = null;
public void setUserCredentials(String userName, String password) {
this.mCredentials = new UsernamePasswordCredentials(userName, password);
}
public void setResponseHandler(ResponseHandler responseHandler){
this.mResponseHandler = responseHandler;
}
public Result<String> delete(String url){
Result<String> result = new Result<T>();
result.setStatus(Result.FAIL);
try {
DefaultHttpClient httpClient = new DefaultHttpClient(mParams);
httpClient.setParams(mParams);
httpClient.getCredentialsProvider().setCredentials(new AuthScope(null, -1), mCredentials);
HttpResponse response = httpClient.execute(new HttpDelete(url));
result.setResult(mResponseHandler.handleResponse(response));
result.setStatus(Result.SUCCESS);
} catch (IllegalArgumentException e) {
e.printStackTrace();
result.setMessage(e.getMessage());
} catch (ClientProtocolException e) {
e.printStackTrace();
result.setMessage(e.getMessage());
} catch (ConnectTimeoutException e) {
result.setMessage("Connection timed out.");
} catch (IOException e) {
e.printStackTrace();
result.setMessage(e.getMessage());
}
return result;
}
UnregisterTask.java
#Override
protected Void doInBackground(String... urls) {
if (urls==null || urls.length!=1)
return null;
String url = urls[0];
HttpUtils httpUtils = new HttpUtils();
httpUtils.setUserCredentials("userid", "password");
httpUtils.setResponseHandler(new UnrgisterHandler());
httpUtils.delete(url);
Result<String> result = aClient.delete(url);
if (result!=null || result.result != null){
//Do Something
}
}
//UnrgisterActivity.java
public void onUnregisterButtonClick(View view){
UnregisterTask task = new UnregisterTask(this);
task.execute(ServerConfig.getIdmServer() + ServerConfig.DELETE_DEVICE + "myid");
}
Error recevied:
Apache Tomcat/7.0.26 - Error report HTTP Status 400 - type Status reportmessage description The request sent by the client was syntactically incorrect ().Apache Tomcat/7.0.26
Thanks in Advance.
I fixed it by myself but I do not understand clearly why the error happened. I changed my code after searching how to set basic authentication.
public Result<T> delete(String url)
Result<T> result = new Result<T>();
result.setStatus(Status.FAIL);
try {
DefaultHttpClient http = new DefaultHttpClient();
if (this.mCredentials!=null){
CredentialsProvider credProvider = new BasicCredentialsProvider();
credProvider.setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), this.mCredentials);
http.setCredentialsProvider(credProvider);
}
HttpDelete delete = new HttpDelete(url);
//delete.setEntity(new StringEntity(data, "UTF8"));
delete.addHeader("Content-type", JSON_TYPE);
HttpResponse response = http.execute(delete);
result.setResult(mResponseHandler.handleResponse(response));
result.setStatus(Result.Status.SUCCESS);
} catch (IllegalArgumentException e) {
e.printStackTrace();
result.setMessage(e.getMessage());
} catch (ClientProtocolException e) {
e.printStackTrace();
result.setMessage(e.getMessage());
} catch (ConnectTimeoutException e) {
result.setMessage("Connection timed out.");
} catch (IOException e) {
e.printStackTrace();
result.setMessage(e.getMessage());
}
return result;
}
A bit still confusing. Anyway, now it works charm.