Java tutorial
/* Copyright 2012 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.mobiperf_library.measurements; import android.net.http.AndroidHttpClient; import android.os.Parcel; import android.os.Parcelable; import android.util.Base64; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.entity.StringEntity; import com.mobiperf_library.Config; import com.mobiperf_library.MeasurementDesc; import com.mobiperf_library.MeasurementResult; import com.mobiperf_library.MeasurementTask; import com.mobiperf_library.MeasurementResult.TaskProgress; import com.mobiperf_library.exceptions.MeasurementError; import com.mobiperf_library.util.Logger; import com.mobiperf_library.util.MeasurementJsonConvertor; import com.mobiperf_library.util.PhoneUtils; import com.mobiperf_library.util.Util; import java.io.IOException; import java.io.InputStream; import java.io.InvalidClassException; import java.net.MalformedURLException; import java.nio.ByteBuffer; import java.security.InvalidParameterException; import java.util.Date; import java.util.Map; /** * A Callable class that performs download throughput test using HTTP get */ public class HttpTask extends MeasurementTask { // Type name for internal use public static final String TYPE = "http"; // Human readable name for the task public static final String DESCRIPTOR = "HTTP"; /* TODO(Wenjie): Depending on state machine configuration of cell tower's radio, * the size to find the 'real' bandwidth of the phone may be network dependent. */ // The maximum number of bytes we will read from requested URL. Set to 1Mb. public static final long MAX_HTTP_RESPONSE_SIZE = 1024 * 1024; // The size of the response body we will report to the service. // If the response is larger than MAX_BODY_SIZE_TO_UPLOAD bytes, we will // only report the first MAX_BODY_SIZE_TO_UPLOAD bytes of the body. public static final int MAX_BODY_SIZE_TO_UPLOAD = 1024; // The buffer size we use to read from the HTTP response stream public static final int READ_BUFFER_SIZE = 1024; // Not used by the HTTP protocol. Just in case we do not receive a status line // from the response public static final int DEFAULT_STATUS_CODE = 0; private AndroidHttpClient httpClient = null; private long duration; public HttpTask(MeasurementDesc desc) { super(new HttpDesc(desc.key, desc.startTime, desc.endTime, desc.intervalSec, desc.count, desc.priority, desc.contextIntervalSec, desc.parameters)); this.duration = Config.DEFAULT_HTTP_TASK_DURATION; } protected HttpTask(Parcel in) { super(in); duration = in.readLong(); } public static final Parcelable.Creator<HttpTask> CREATOR = new Parcelable.Creator<HttpTask>() { public HttpTask createFromParcel(Parcel in) { return new HttpTask(in); } public HttpTask[] newArray(int size) { return new HttpTask[size]; } }; @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeLong(duration); } /** * The description of a HTTP measurement */ public static class HttpDesc extends MeasurementDesc { public String url; private String method; private String headers; private String body; public HttpDesc(String key, Date startTime, Date endTime, double intervalSec, long count, long priority, int contextIntervalSec, Map<String, String> params) throws InvalidParameterException { super(HttpTask.TYPE, key, startTime, endTime, intervalSec, count, priority, contextIntervalSec, params); initializeParams(params); if (this.url == null || this.url.length() == 0) { throw new InvalidParameterException("URL for http task is null"); } } @Override protected void initializeParams(Map<String, String> params) { if (params == null) { return; } this.url = params.get("url"); if (!this.url.startsWith("http://") && !this.url.startsWith("https://")) { this.url = "http://" + this.url; } this.method = params.get("method"); if (this.method == null || this.method.isEmpty()) { this.method = "get"; } this.headers = params.get("headers"); this.body = params.get("body"); } @Override public String getType() { return HttpTask.TYPE; } protected HttpDesc(Parcel in) { super(in); url = in.readString(); method = in.readString(); headers = in.readString(); body = in.readString(); } public static final Parcelable.Creator<HttpDesc> CREATOR = new Parcelable.Creator<HttpDesc>() { public HttpDesc createFromParcel(Parcel in) { return new HttpDesc(in); } public HttpDesc[] newArray(int size) { return new HttpDesc[size]; } }; @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeString(url); dest.writeString(method); dest.writeString(headers); dest.writeString(body); } } /** * Returns a copy of the HttpTask */ @Override public MeasurementTask clone() { MeasurementDesc desc = this.measurementDesc; HttpDesc newDesc = new HttpDesc(desc.key, desc.startTime, desc.endTime, desc.intervalSec, desc.count, desc.priority, desc.contextIntervalSec, desc.parameters); return new HttpTask(newDesc); } /** Runs the HTTP measurement task. Will acquire power lock to ensure wifi * is not turned off */ @Override public MeasurementResult[] call() throws MeasurementError { int statusCode = HttpTask.DEFAULT_STATUS_CODE; long duration = 0; long originalHeadersLen = 0; long originalBodyLen; String headers = null; ByteBuffer body = ByteBuffer.allocate(HttpTask.MAX_BODY_SIZE_TO_UPLOAD); // boolean success = false; TaskProgress taskProgress = TaskProgress.FAILED; String errorMsg = ""; InputStream inputStream = null; try { // set the download URL, a URL that points to a file on the Internet // this is the file to be downloaded HttpDesc task = (HttpDesc) this.measurementDesc; String urlStr = task.url; // TODO(Wenjie): Need to set timeout for the HTTP methods httpClient = AndroidHttpClient.newInstance(Util.prepareUserAgent()); HttpRequestBase request = null; if (task.method.compareToIgnoreCase("head") == 0) { request = new HttpHead(urlStr); } else if (task.method.compareToIgnoreCase("get") == 0) { request = new HttpGet(urlStr); } else if (task.method.compareToIgnoreCase("post") == 0) { request = new HttpPost(urlStr); HttpPost postRequest = (HttpPost) request; postRequest.setEntity(new StringEntity(task.body)); } else { // Use GET by default request = new HttpGet(urlStr); } if (task.headers != null && task.headers.trim().length() > 0) { for (String headerLine : task.headers.split("\r\n")) { String tokens[] = headerLine.split(":"); if (tokens.length == 2) { request.addHeader(tokens[0], tokens[1]); } else { throw new MeasurementError("Incorrect header line: " + headerLine); } } } byte[] readBuffer = new byte[HttpTask.READ_BUFFER_SIZE]; int readLen; int totalBodyLen = 0; long startTime = System.currentTimeMillis(); HttpResponse response = httpClient.execute(request); /* TODO(Wenjie): HttpClient does not automatically handle the following codes * 301 Moved Permanently. HttpStatus.SC_MOVED_PERMANENTLY * 302 Moved Temporarily. HttpStatus.SC_MOVED_TEMPORARILY * 303 See Other. HttpStatus.SC_SEE_OTHER * 307 Temporary Redirect. HttpStatus.SC_TEMPORARY_REDIRECT * * We may want to fetch instead from the redirected page. */ StatusLine statusLine = response.getStatusLine(); if (statusLine != null) { statusCode = statusLine.getStatusCode(); if (statusCode == 200) { taskProgress = TaskProgress.COMPLETED; } else { taskProgress = TaskProgress.FAILED; } } /* For HttpClient to work properly, we still want to consume the entire * response even if the status code is not 200 */ HttpEntity responseEntity = response.getEntity(); originalBodyLen = responseEntity.getContentLength(); long expectedResponseLen = HttpTask.MAX_HTTP_RESPONSE_SIZE; // getContentLength() returns negative number if body length is unknown if (originalBodyLen > 0) { expectedResponseLen = originalBodyLen; } if (responseEntity != null) { inputStream = responseEntity.getContent(); while ((readLen = inputStream.read(readBuffer)) > 0 && totalBodyLen <= HttpTask.MAX_HTTP_RESPONSE_SIZE) { totalBodyLen += readLen; // Fill in the body to report up to MAX_BODY_SIZE if (body.remaining() > 0) { int putLen = body.remaining() < readLen ? body.remaining() : readLen; body.put(readBuffer, 0, putLen); } } duration = System.currentTimeMillis() - startTime;//TODO check this } Header[] responseHeaders = response.getAllHeaders(); if (responseHeaders != null) { headers = ""; for (Header hdr : responseHeaders) { /* * TODO(Wenjie): There can be preceding and trailing white spaces in * each header field. I cannot find internal methods that return the * number of bytes in a header. The solution here assumes the encoding * is one byte per character. */ originalHeadersLen += hdr.toString().length(); headers += hdr.toString() + "\r\n"; } } PhoneUtils phoneUtils = PhoneUtils.getPhoneUtils(); MeasurementResult result = new MeasurementResult(phoneUtils.getDeviceInfo().deviceId, phoneUtils.getDeviceProperty(), HttpTask.TYPE, System.currentTimeMillis() * 1000, taskProgress, this.measurementDesc); result.addResult("code", statusCode); if (taskProgress == TaskProgress.COMPLETED) { result.addResult("time_ms", duration); result.addResult("headers_len", originalHeadersLen); result.addResult("body_len", totalBodyLen); result.addResult("headers", headers); result.addResult("body", Base64.encodeToString(body.array(), Base64.DEFAULT)); } Logger.i(MeasurementJsonConvertor.toJsonString(result)); MeasurementResult[] mrArray = new MeasurementResult[1]; mrArray[0] = result; return mrArray; } catch (MalformedURLException e) { errorMsg += e.getMessage() + "\n"; Logger.e(e.getMessage()); } catch (IOException e) { errorMsg += e.getMessage() + "\n"; Logger.e(e.getMessage()); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { Logger.e("Fails to close the input stream from the HTTP response"); } } if (httpClient != null) { httpClient.close(); } } throw new MeasurementError("Cannot get result from HTTP measurement because " + errorMsg); } @SuppressWarnings("rawtypes") public static Class getDescClass() throws InvalidClassException { return HttpDesc.class; } @Override public String getType() { return HttpTask.TYPE; } @Override public String getDescriptor() { return DESCRIPTOR; } @Override public String toString() { HttpDesc desc = (HttpDesc) measurementDesc; return "[HTTP " + desc.method + "]\n Target: " + desc.url + "\n Interval (sec): " + desc.intervalSec + "\n Next run: " + desc.startTime; } @Override public boolean stop() { return false; } @Override public long getDuration() { return this.duration; } @Override public void setDuration(long newDuration) { if (newDuration < 0) { this.duration = 0; } else { this.duration = newDuration; } } }