/* * Copyright 2011 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.google.ipc.invalidation.ticl.android; import com.google.common.base.Preconditions; import com.google.ipc.invalidation.external.client.SystemResources.Logger; import com.google.protos.ipc.invalidation.AndroidChannel.AddressedAndroidMessage; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.BasicResponseHandler; import java.io.IOException; /** * Implementation of the HTTP communication used by {@code AndroidChannel}. Factored into * a separate class that can be run outside the Android environment to improve testing. * */ public abstract class AndroidChannelBase { /** Http client to use when making requests to . */ HttpClient httpClient; /** Authentication type for frontends. */ private final String authType; /** URL of the frontends. */ private final String channelUrl; /** The token that will be echoed to the data center in the headers of all HTTP requests. */ private String echoToken = null; /** * Creates an instance that uses {@code httpClient} to send requests to {@code channelUrl} * using an auth type of {@code authType}. */ protected AndroidChannelBase(HttpClient httpClient, String authType, String channelUrl) { this.httpClient = httpClient; this.authType = authType; this.channelUrl = channelUrl; } /** Sends {@code outgoingMessage} to . */ void deliverOutboundMessage(final byte[] outgoingMessage) { getLogger().fine("Delivering outbound message: %s bytes", outgoingMessage.length); StringBuilder target = new StringBuilder(); // Build base URL that targets the inbound request service with the encoded network endpoint id target.append(channelUrl); target.append(AndroidHttpConstants.REQUEST_URL); target.append(getWebEncodedEndpointId()); // Add query parameter indicating the service to authenticate against target.append('?'); target.append(AndroidHttpConstants.SERVICE_PARAMETER); target.append('='); target.append(authType); // Construct entity containing the outbound protobuf msg ByteArrayEntity contentEntity = new ByteArrayEntity(outgoingMessage); contentEntity.setContentType(AndroidHttpConstants.PROTO_CONTENT_TYPE); // Construct POST request with the entity content and appropriate authorization HttpPost httpPost = new HttpPost(target.toString()); httpPost.setEntity(contentEntity); setPostHeaders(httpPost); try { String response = httpClient.execute(httpPost, new BasicResponseHandler()); } catch (ClientProtocolException exception) { // TODO: Distinguish between key HTTP error codes and handle more specifically // where appropriate. getLogger().warning("Error from server on request: %s", exception); } catch (IOException exception) { getLogger().warning("Error writing request: %s", exception); } catch (RuntimeException exception) { getLogger().warning("Runtime exception writing request: %s", exception); } } /** Sets the Authorization and echo headers on {@code httpPost}. */ private void setPostHeaders(HttpPost httpPost) { httpPost.setHeader("Authorization", "GoogleLogin auth=" + getAuthToken()); if (echoToken != null) { // If we have a token to echo to the server, echo it. httpPost.setHeader(AndroidHttpConstants.ECHO_HEADER, echoToken); } } /** * If {@code echoToken} is not {@code null}, updates the token that will be sent in the header * of all HTTP requests. */ void updateEchoToken(String echoToken) { if (echoToken != null) { this.echoToken = echoToken; } } /** Returns the token that will be sent in the header of all HTTP requests. */ String getEchoTokenForTest() { return this.echoToken; } /** Sets the HTTP client to {@code client}. */ void setHttpClientForTest(HttpClient client) { this.httpClient = Preconditions.checkNotNull(client); } /** Returns the base-64-encoded network endpoint id for the client. */ protected abstract String getWebEncodedEndpointId(); /** Returns the current authentication token for the client for web requests to . */ protected abstract String getAuthToken(); /** Returns the logger to use. */ protected abstract Logger getLogger(); /** Attempts to deliver a {@code message} from to the local client. */ protected abstract void tryDeliverMessage(AddressedAndroidMessage message); }