com.mobiperf.speedometer.measurements.HttpTask.java Source code

Java tutorial

Introduction

Here is the source code for com.mobiperf.speedometer.measurements.HttpTask.java

Source

/* 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.speedometer.measurements;

import com.mobiperf.speedometer.Config;
import com.mobiperf.speedometer.Logger;
import com.mobiperf.speedometer.MeasurementDesc;
import com.mobiperf.speedometer.MeasurementError;
import com.mobiperf.speedometer.MeasurementResult;
import com.mobiperf.speedometer.MeasurementTask;
import com.mobiperf.util.MeasurementJsonConvertor;
import com.mobiperf.util.PhoneUtils;
import com.mobiperf.util.Util;

import android.content.Context;
import android.net.http.AndroidHttpClient;
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 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
 * 
 * @author wenjiezeng@google.com (Steve Zeng)
 * 
 */
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 * 10;
    // 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;

    public HttpTask(MeasurementDesc desc, Context parent) {
        super(new HttpDesc(desc.key, desc.startTime, desc.endTime, desc.intervalSec, desc.count, desc.priority,
                desc.parameters), parent);
    }

    /**
     * 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,
                Map<String, String> params) throws InvalidParameterException {
            super(HttpTask.TYPE, key, startTime, endTime, intervalSec, count, priority, params, null, null);
            initalizeParams(params);
            if (this.url == null || this.url.length() == 0) {
                throw new InvalidParameterException("URL for http task is null");
            }
        }

        @Override
        protected void initalizeParams(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;
            }
            if ((this.method = params.get("method")) == null) {
                this.method = "get";
            }

            this.headers = params.get("headers");
            this.body = params.get("body");
        }

        @Override
        public String getType() {
            return HttpTask.TYPE;
        }

    }

    /**
     * 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.parameters);
        return new HttpTask(newDesc, parent);
    }

    /**
     * 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;
        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(this.parent));
            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();
                success = (statusCode == 200);
                Logger.i(">>> success = " + success + " " + statusCode);
            } else {
                Logger.i(">>> statusLine is null");
            }

            /*
             * 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);
                    //          }
                    this.progress = (int) (100 * totalBodyLen / expectedResponseLen);
                    this.progress = Math.min(Config.MAX_PROGRESS_BAR_VALUE, progress);
                    broadcastProgressForUser(this.progress);
                }
                duration = System.currentTimeMillis() - startTime;
            }

            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, success,
                    this.measurementDesc);

            result.addResult("code", statusCode);

            if (success) {
                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));
            return result;
        } 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 void stop() {
        if (httpClient != null) {
            httpClient.close();
        }
    }
}