1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.cooliris.picasa; 18 19 import java.io.ByteArrayOutputStream; 20 import java.io.IOException; 21 import java.io.InputStream; 22 import java.util.zip.GZIPInputStream; 23 import java.util.zip.GZIPOutputStream; 24 25 import org.apache.http.Header; 26 import org.apache.http.HttpEntity; 27 import org.apache.http.HttpResponse; 28 import org.apache.http.client.methods.HttpGet; 29 import org.apache.http.client.methods.HttpPost; 30 import org.apache.http.client.methods.HttpUriRequest; 31 import org.apache.http.client.params.HttpClientParams; 32 import org.apache.http.conn.scheme.PlainSocketFactory; 33 import org.apache.http.conn.scheme.Scheme; 34 import org.apache.http.conn.scheme.SchemeRegistry; 35 import org.apache.http.entity.ByteArrayEntity; 36 import org.apache.http.entity.InputStreamEntity; 37 import org.apache.http.impl.client.DefaultHttpClient; 38 import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; 39 import org.apache.http.params.BasicHttpParams; 40 import org.apache.http.params.HttpConnectionParams; 41 import org.apache.http.params.HttpParams; 42 import org.apache.http.params.HttpProtocolParams; 43 44 import android.text.TextUtils; 45 import android.util.Log; 46 47 public final class GDataClient { 48 private static final String TAG = "GDataClient"; 49 private static final String USER_AGENT = "Cooliris-GData/1.0; gzip"; 50 private static final String X_HTTP_METHOD_OVERRIDE = "X-HTTP-Method-Override"; 51 private static final String IF_MATCH = "If-Match"; 52 private static final int CONNECTION_TIMEOUT = 20000; // ms. 53 private static final int MIN_GZIP_SIZE = 512; 54 public static final HttpParams HTTP_PARAMS; 55 public static final ThreadSafeClientConnManager HTTP_CONNECTION_MANAGER; 56 57 private final DefaultHttpClient mHttpClient = new DefaultHttpClient(HTTP_CONNECTION_MANAGER, HTTP_PARAMS); 58 private String mAuthToken; 59 60 static { 61 // Prepare HTTP parameters. 62 HttpParams params = new BasicHttpParams(); 63 HttpConnectionParams.setStaleCheckingEnabled(params, false); 64 HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT); 65 HttpConnectionParams.setSoTimeout(params, CONNECTION_TIMEOUT); 66 HttpClientParams.setRedirecting(params, true); 67 HttpProtocolParams.setUserAgent(params, USER_AGENT); 68 HTTP_PARAMS = params; 69 70 // Register HTTP protocol. 71 SchemeRegistry schemeRegistry = new SchemeRegistry(); 72 schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); 73 74 // Create the connection manager. 75 HTTP_CONNECTION_MANAGER = new ThreadSafeClientConnManager(params, schemeRegistry); 76 } 77 78 public static final class Operation { 79 public String inOutEtag; 80 public int outStatus; 81 public InputStream outBody; 82 } 83 84 public void setAuthToken(String authToken) { 85 mAuthToken = authToken; 86 } 87 88 public void get(String feedUrl, Operation operation) throws IOException { 89 callMethod(new HttpGet(feedUrl), operation); 90 } 91 92 public void post(String feedUrl, byte[] data, String contentType, Operation operation) throws IOException { 93 ByteArrayEntity entity = getCompressedEntity(data); 94 entity.setContentType(contentType); 95 HttpPost post = new HttpPost(feedUrl); 96 post.setEntity(entity); 97 callMethod(post, operation); 98 } 99 100 public void put(String feedUrl, byte[] data, String contentType, Operation operation) throws IOException { 101 ByteArrayEntity entity = getCompressedEntity(data); 102 entity.setContentType(contentType); 103 HttpPost post = new HttpPost(feedUrl); 104 post.setHeader(X_HTTP_METHOD_OVERRIDE, "PUT"); 105 post.setEntity(entity); 106 callMethod(post, operation); 107 } 108 109 public void putStream(String feedUrl, InputStream stream, String contentType, Operation operation) throws IOException { 110 InputStreamEntity entity = new InputStreamEntity(stream, -1); 111 entity.setContentType(contentType); 112 HttpPost post = new HttpPost(feedUrl); 113 post.setHeader(X_HTTP_METHOD_OVERRIDE, "PUT"); 114 post.setEntity(entity); 115 callMethod(post, operation); 116 } 117 118 public void delete(String feedUrl, Operation operation) throws IOException { 119 HttpPost post = new HttpPost(feedUrl); 120 String etag = operation.inOutEtag; 121 post.setHeader(X_HTTP_METHOD_OVERRIDE, "DELETE"); 122 post.setHeader(IF_MATCH, etag != null ? etag : "*"); 123 callMethod(post, operation); 124 } 125 126 private void callMethod(HttpUriRequest request, Operation operation) throws IOException { 127 // Specify GData protocol version 2.0. 128 request.addHeader("GData-Version", "2"); 129 130 // Indicate support for gzip-compressed responses. 131 request.addHeader("Accept-Encoding", "gzip"); 132 133 // Specify authorization token if provided. 134 String authToken = mAuthToken; 135 if (!TextUtils.isEmpty(authToken)) { 136 request.addHeader("Authorization", "GoogleLogin auth=" + authToken); 137 } 138 139 // Specify the ETag of a prior response, if available. 140 String etag = operation.inOutEtag; 141 if (etag != null) { 142 request.addHeader("If-None-Match", etag); 143 } 144 145 // Execute the HTTP request. 146 HttpResponse httpResponse = null; 147 try { 148 httpResponse = mHttpClient.execute(request); 149 } catch (IOException e) { 150 Log.w(TAG, "Request failed: " + request.getURI()); 151 throw e; 152 } 153 154 // Get the status code and response body. 155 int status = httpResponse.getStatusLine().getStatusCode(); 156 InputStream stream = null; 157 HttpEntity entity = httpResponse.getEntity(); 158 if (entity != null) { 159 // Wrap the entity input stream in a GZIP decoder if necessary. 160 stream = entity.getContent(); 161 if (stream != null) { 162 Header header = entity.getContentEncoding(); 163 if (header != null) { 164 if (header.getValue().contains("gzip")) { 165 stream = new GZIPInputStream(stream); 166 } 167 } 168 } 169 } 170 171 // Return the stream if successful. 172 Header etagHeader = httpResponse.getFirstHeader("ETag"); 173 operation.outStatus = status; 174 operation.inOutEtag = etagHeader != null ? etagHeader.getValue() : null; 175 operation.outBody = stream; 176 } 177 178 private ByteArrayEntity getCompressedEntity(byte[] data) throws IOException { 179 ByteArrayEntity entity; 180 if (data.length >= MIN_GZIP_SIZE) { 181 ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(data.length / 2); 182 GZIPOutputStream gzipOutput = new GZIPOutputStream(byteOutput); 183 gzipOutput.write(data); 184 gzipOutput.close(); 185 entity = new ByteArrayEntity(byteOutput.toByteArray()); 186 } else { 187 entity = new ByteArrayEntity(data); 188 } 189 return entity; 190 } 191 192 public static String inputStreamToString(InputStream stream) { 193 if (stream != null) { 194 try { 195 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 196 byte[] buffer = new byte[4096]; 197 int bytesRead; 198 while ((bytesRead = stream.read(buffer)) != -1) { 199 bytes.write(buffer, 0, bytesRead); 200 } 201 return new String(bytes.toString()); 202 } catch (IOException e) { 203 // Fall through and return null. 204 } 205 } 206 return null; 207 } 208 } 209