From 0a40f846244c9dd469a0ae5b076cc30142a10bbe Mon Sep 17 00:00:00 2001 From: Sarah Lensing Date: Fri, 30 Jun 2017 15:16:58 -0400 Subject: [PATCH] Send User-Agent (#412) * Send User-Agent for MapzenMap requests & create generic http handler * Send User-Agent for MapzenSearch requests * Send User-Agent for all MapzenRouter requests * Rm trailing ; --- config/checkstyle/checkstyle-suppressions.xml | 1 + core/build.gradle | 3 + .../android/core/CoreAndroidModule.java | 18 +- .../android/core/GenericHttpHandler.java | 28 +++ .../android/graphics/MapInitializer.java | 8 +- .../graphics/MapzenMapHttpHandler.java | 98 ++++++++++ .../android/graphics/TileHttpHandler.java | 51 ----- .../android/graphics/TmpHttpHandler.java | 184 ++++++++++++++++++ .../search/MapzenSearchHttpHandler.java | 75 +++++++ .../android/search/SearchInitializer.java | 22 ++- .../android/search/SearchRequestHandler.java | 40 ---- .../com/mapzen/android/core/TestVals.java | 21 ++ .../android/graphics/MapInitializerTest.java | 2 +- .../graphics/MapzenMapHttpHandlerTest.java | 49 +++++ .../android/graphics/TileHttpHandlerTest.java | 13 -- .../search/MapzenSearchHttpHandlerTest.java | 66 +++++++ .../android/search/MapzenSearchTest.java | 2 +- .../android/search/SearchInitializerTest.java | 3 +- .../search/SearchRequestHandlerTest.java | 23 --- .../mapzen/android/core/AndroidModule.java | 8 +- .../mapzen/android/routing/MapzenRouter.java | 4 +- .../routing/MapzenRouterHttpHandler.java | 135 +++++++++++++ .../android/routing/RouterInitializer.java | 51 +++++ .../routing/TurnByTurnHttpHandler.java | 64 ------ .../routing/MapzenRouterHttpHandlerTest.java | 67 +++++++ .../routing/RouterInitializerTest.java | 56 ++++++ 26 files changed, 877 insertions(+), 215 deletions(-) create mode 100644 core/src/main/java/com/mapzen/android/core/GenericHttpHandler.java create mode 100644 core/src/main/java/com/mapzen/android/graphics/MapzenMapHttpHandler.java delete mode 100644 core/src/main/java/com/mapzen/android/graphics/TileHttpHandler.java create mode 100644 core/src/main/java/com/mapzen/android/graphics/TmpHttpHandler.java create mode 100644 core/src/main/java/com/mapzen/android/search/MapzenSearchHttpHandler.java delete mode 100644 core/src/main/java/com/mapzen/android/search/SearchRequestHandler.java create mode 100644 core/src/test/java/com/mapzen/android/core/TestVals.java create mode 100644 core/src/test/java/com/mapzen/android/graphics/MapzenMapHttpHandlerTest.java delete mode 100644 core/src/test/java/com/mapzen/android/graphics/TileHttpHandlerTest.java create mode 100644 core/src/test/java/com/mapzen/android/search/MapzenSearchHttpHandlerTest.java delete mode 100644 core/src/test/java/com/mapzen/android/search/SearchRequestHandlerTest.java create mode 100644 mapzen-android-sdk/src/main/java/com/mapzen/android/routing/MapzenRouterHttpHandler.java create mode 100644 mapzen-android-sdk/src/main/java/com/mapzen/android/routing/RouterInitializer.java delete mode 100644 mapzen-android-sdk/src/main/java/com/mapzen/android/routing/TurnByTurnHttpHandler.java create mode 100644 mapzen-android-sdk/src/test/java/com/mapzen/android/routing/MapzenRouterHttpHandlerTest.java create mode 100644 mapzen-android-sdk/src/test/java/com/mapzen/android/routing/RouterInitializerTest.java diff --git a/config/checkstyle/checkstyle-suppressions.xml b/config/checkstyle/checkstyle-suppressions.xml index 803c4ac2..6141b18b 100644 --- a/config/checkstyle/checkstyle-suppressions.xml +++ b/config/checkstyle/checkstyle-suppressions.xml @@ -18,4 +18,5 @@ + diff --git a/core/build.gradle b/core/build.gradle index d118cf66..8bd86441 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -21,6 +21,8 @@ project.archivesBaseName = POM_ARTIFACT_ID ext.tangram_version = "0.6.3" +def SDK_VERSION = hasProperty('version') ? '"' + version + '"' : "null"; + android { compileSdkVersion 25 buildToolsVersion '25.0.2' @@ -31,6 +33,7 @@ android { versionCode 1 versionName "1.0" resValue "string", "tangram_version", "${tangram_version}" + buildConfigField "String", "SDK_VERSION", SDK_VERSION } buildTypes { diff --git a/core/src/main/java/com/mapzen/android/core/CoreAndroidModule.java b/core/src/main/java/com/mapzen/android/core/CoreAndroidModule.java index 3c49f2cc..2d473bde 100644 --- a/core/src/main/java/com/mapzen/android/core/CoreAndroidModule.java +++ b/core/src/main/java/com/mapzen/android/core/CoreAndroidModule.java @@ -1,11 +1,13 @@ package com.mapzen.android.core; -import com.mapzen.android.graphics.TileHttpHandler; +import com.mapzen.android.graphics.MapzenMapHttpHandler; import com.mapzen.android.search.SearchInitializer; import android.content.Context; import android.content.res.Resources; +import java.util.Map; + import javax.inject.Singleton; import dagger.Module; @@ -42,10 +44,18 @@ } /** - * Provides HTTP handler to append API key to outgoing vector tile requests. + * Provides HTTP handler to configure User-Agent for outgoing vector tile requests. */ - @Provides @Singleton public TileHttpHandler provideTileHttpHandler() { - return new TileHttpHandler(); + @Provides @Singleton public MapzenMapHttpHandler provideTileHttpHandler() { + return new MapzenMapHttpHandler() { + @Override public Map queryParamsForRequest() { + return null; + } + + @Override public Map headersForRequest() { + return null; + } + }; } /** diff --git a/core/src/main/java/com/mapzen/android/core/GenericHttpHandler.java b/core/src/main/java/com/mapzen/android/core/GenericHttpHandler.java new file mode 100644 index 00000000..3d8c1e15 --- /dev/null +++ b/core/src/main/java/com/mapzen/android/core/GenericHttpHandler.java @@ -0,0 +1,28 @@ +package com.mapzen.android.core; + +import com.mapzen.BuildConfig; + +import android.os.Build; + +import java.util.Map; + +/** + * Generic SDK interface for service-specific handlers to implement. + */ +public interface GenericHttpHandler { + + String HEADER_USER_AGENT = "User-Agent"; + String USER_AGENT = "android-sdk;" + BuildConfig.SDK_VERSION + ";" + Build.VERSION.RELEASE; + + /** + * Return query parameters to be appended to every request. + * @return + */ + Map queryParamsForRequest(); + + /** + * Return headers to be added to every request. + * @return + */ + Map headersForRequest(); +} diff --git a/core/src/main/java/com/mapzen/android/graphics/MapInitializer.java b/core/src/main/java/com/mapzen/android/graphics/MapInitializer.java index 3dabd737..3a9532f9 100644 --- a/core/src/main/java/com/mapzen/android/graphics/MapInitializer.java +++ b/core/src/main/java/com/mapzen/android/graphics/MapInitializer.java @@ -21,7 +21,7 @@ public class MapInitializer { private Context context; - private TileHttpHandler tileHttpHandler; + private MapzenMapHttpHandler mapzenMapHttpHandler; private MapDataManager mapDataManager; @@ -34,11 +34,11 @@ public class MapInitializer { /** * Creates a new instance. */ - @Inject MapInitializer(Context context, TileHttpHandler tileHttpHandler, + @Inject MapInitializer(Context context, MapzenMapHttpHandler mapzenMapHttpHandler, MapDataManager mapDataManager, MapStateManager mapStateManager, SceneUpdateManager sceneUpdateManager) { this.context = context; - this.tileHttpHandler = tileHttpHandler; + this.mapzenMapHttpHandler = mapzenMapHttpHandler; this.mapDataManager = mapDataManager; this.mapStateManager = mapStateManager; this.sceneUpdateManager = sceneUpdateManager; @@ -92,7 +92,7 @@ private void loadMap(final MapView mapView, String sceneFile, final OnMapReadyCa mapStateManager.isPathOverlayEnabled()); getTangramView(mapView).getMapAsync(new com.mapzen.tangram.MapView.OnMapReadyCallback() { @Override public void onMapReady(MapController mapController) { - mapController.setHttpHandler(tileHttpHandler); + mapController.setHttpHandler(mapzenMapHttpHandler.httpHandler()); MapzenManager mapzenManager = MapzenManager.instance(mapView.getContext()); callback.onMapReady( new MapzenMap(mapView, mapController, new OverlayManager(mapView, mapController, diff --git a/core/src/main/java/com/mapzen/android/graphics/MapzenMapHttpHandler.java b/core/src/main/java/com/mapzen/android/graphics/MapzenMapHttpHandler.java new file mode 100644 index 00000000..c8302894 --- /dev/null +++ b/core/src/main/java/com/mapzen/android/graphics/MapzenMapHttpHandler.java @@ -0,0 +1,98 @@ +package com.mapzen.android.graphics; + +import com.mapzen.android.core.GenericHttpHandler; +import com.mapzen.tangram.HttpHandler; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import okhttp3.Callback; +import okhttp3.Headers; +import okhttp3.OkHttpClient; +import okhttp3.Request; + +/** + * Base class for HTTP requests made by {@link MapzenMap}. + */ +public abstract class MapzenMapHttpHandler implements GenericHttpHandler { + + private HttpHandler httpHandler; + RequestEnqueuer requestEnqueuer; + + /** + * Public constructor with no cache configured. + */ + public MapzenMapHttpHandler() { + this(null, 0); + } + + /** + * Public constructor with cache configured. + * @param directory cache directory + * @param maxSize max cache directory size in bytes + */ + public MapzenMapHttpHandler(File directory, long maxSize) { + httpHandler = new InternalHttpHandler(directory, maxSize); + requestEnqueuer = new RequestEnqueuer(); + } + + /** + * Underlying Tangram handler. + * @return + */ + HttpHandler httpHandler() { + return httpHandler; + } + + private class InternalHttpHandler extends TmpHttpHandler { + + public InternalHttpHandler(File directory, long maxSize) { + super(directory, maxSize); + } + + @Override public boolean onRequest(String url, Callback cb) { + Map customParams = queryParamsForRequest(); + if (customParams != null) { + for (String key : customParams.keySet()) { + url = url.concat(key + "=" + customParams.get(key) + "&"); + } + } + + Map headers = new HashMap<>(); + headers.put(HEADER_USER_AGENT, USER_AGENT); + Map customHeaders = headersForRequest(); + if (customHeaders != null) { + headers.putAll(customHeaders); + } + + requestEnqueuer.enqueueRequest(okClient, cb, url, headers); + return true; + } + + @Override public void onCancel(String url) { + super.onCancel(url); + } + } + + /** + * Class to handle creating and enqueuing requests. + */ + class RequestEnqueuer { + /** + * Creates and enqueues a {@link Request}. + * @param client + * @param callback + * @param url + * @param headers + */ + void enqueueRequest(OkHttpClient client, Callback callback, String url, + Map headers) { + Request request = new Request.Builder() + .url(url) + .headers(Headers.of(headers)) + .build(); + client.newCall(request).enqueue(callback); + } + } +} diff --git a/core/src/main/java/com/mapzen/android/graphics/TileHttpHandler.java b/core/src/main/java/com/mapzen/android/graphics/TileHttpHandler.java deleted file mode 100644 index 0937ca93..00000000 --- a/core/src/main/java/com/mapzen/android/graphics/TileHttpHandler.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.mapzen.android.graphics; - -import com.mapzen.tangram.HttpHandler; - -import android.util.Log; - -import java.io.IOException; -import java.util.HashMap; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Response; - -/** - * A handler responsible for appending an API key to vector tile requests. - */ -public class TileHttpHandler extends HttpHandler { - private static final String TAG = TileHttpHandler.class.getSimpleName(); - private static final boolean DEBUG_REQUESTS = false; - - private HashMap urlToMillis = new HashMap<>(); - - @Override public boolean onRequest(final String url, Callback cb) { - final Callback finalCallback = cb; - final Callback internalCallback = new Callback() { - @Override public void onFailure(Call call, IOException e) { - urlToMillis.remove(url); - finalCallback.onFailure(call, e); - } - - @Override public void onResponse(Call call, Response response) throws IOException { - if (DEBUG_REQUESTS) { - Long endMillis = System.currentTimeMillis(); - Long startMillis = urlToMillis.get(url); - Long elapsedMillis = endMillis - startMillis; - boolean fromCache = response.cacheResponse() != null; - if (fromCache) { - Log.d(TAG, url + " - loaded from cache [" + elapsedMillis + "] millis"); - } else { - Log.d(TAG, url + " - loaded from network [" + elapsedMillis + "] millis"); - } - } - urlToMillis.remove(url); - finalCallback.onResponse(call, response); - } - }; - - urlToMillis.put(url, System.currentTimeMillis()); - return super.onRequest(url, internalCallback); - } -} diff --git a/core/src/main/java/com/mapzen/android/graphics/TmpHttpHandler.java b/core/src/main/java/com/mapzen/android/graphics/TmpHttpHandler.java new file mode 100644 index 00000000..4a1adf6f --- /dev/null +++ b/core/src/main/java/com/mapzen/android/graphics/TmpHttpHandler.java @@ -0,0 +1,184 @@ +package com.mapzen.android.graphics; + +import com.mapzen.tangram.HttpHandler; + +import android.os.Build; + +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +import okhttp3.Cache; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.ConnectionSpec; +import okhttp3.Headers; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.TlsVersion; + +/** + * Temporary {@link MapzenMapHttpHandler.InternalHttpHandler} super class until Tangram allows + * customizing headers. + * https://github.com/tangrams/tangram-es/issues/1542 + */ +class TmpHttpHandler extends HttpHandler { + + OkHttpClient okClient; + + /** + * Enables TLS v1.2 when creating SSLSockets. + *

+ * For some reason, android supports TLS v1.2 from API 16, but enables it by + * default only from API 20. + * + * @link https://developer.android.com/reference/javax/net/ssl/SSLSocket.html + * @see SSLSocketFactory + */ + private class Tls12SocketFactory extends SSLSocketFactory { + private final String[] TLS_V12_ONLY = {"TLSv1.2"}; + + final SSLSocketFactory delegate; + + public Tls12SocketFactory(SSLSocketFactory base) { + this.delegate = base; + } + + @Override + public String[] getDefaultCipherSuites() { + return delegate.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return delegate.getSupportedCipherSuites(); + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws + IOException { + return patch(delegate.createSocket(s, host, port, autoClose)); + } + + @Override + public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + return patch(delegate.createSocket(host, port)); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { + return patch(delegate.createSocket(host, port, localHost, localPort)); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return patch(delegate.createSocket(host, port)); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + return patch(delegate.createSocket(address, port, localAddress, localPort)); + } + + private Socket patch(Socket s) { + if (s instanceof SSLSocket) { + ((SSLSocket) s).setEnabledProtocols(TLS_V12_ONLY); + } + return s; + } + } + + /** + * Construct an {@code HttpHandler} with default options. + */ + public TmpHttpHandler() { + this(null, 0); + } + + /** + * Construct an {@code HttpHandler} with cache. + * Cache map data in a directory with a specified size limit + * @param directory Directory in which map data will be cached + * @param maxSize Maximum size of data to cache, in bytes + */ + public TmpHttpHandler(File directory, long maxSize) { + OkHttpClient.Builder builder = new OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS); + + if (directory != null && maxSize > 0) { + builder.cache(new Cache(directory, maxSize)); + } + + if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 22) { + try { + SSLContext sc = SSLContext.getInstance("TLSv1.2"); + sc.init(null, null, null); + builder.sslSocketFactory(new Tls12SocketFactory(sc.getSocketFactory())); + + ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) + .tlsVersions(TlsVersion.TLS_1_2) + .build(); + + List specs = new ArrayList<>(); + specs.add(cs); + specs.add(ConnectionSpec.COMPATIBLE_TLS); + specs.add(ConnectionSpec.CLEARTEXT); + + builder.connectionSpecs(specs); + } catch (Exception exc) { + android.util.Log.e("Tangram", "Error while setting TLS 1.2", exc); + } + } + + okClient = builder.build(); + } + + /** + * Begin an HTTP request + * @param url URL for the requested resource + * @param cb Callback for handling request result + * @return true if request was successfully started + */ + public boolean onRequest(String url, Map headers, Callback cb) { + Request request = new Request.Builder() + .url(url) + .headers(Headers.of(headers)) + .build(); + okClient.newCall(request).enqueue(cb); + return true; + } + + /** + * Cancel an HTTP request + * @param url URL of the request to be cancelled + */ + public void onCancel(String url) { + + // check and cancel running call + for (Call runningCall : okClient.dispatcher().runningCalls()) { + if (runningCall.request().url().toString().equals(url)) { + runningCall.cancel(); + } + } + + // check and cancel queued call + for (Call queuedCall : okClient.dispatcher().queuedCalls()) { + if (queuedCall.request().url().toString().equals(url)) { + queuedCall.cancel(); + } + } + } + +} diff --git a/core/src/main/java/com/mapzen/android/search/MapzenSearchHttpHandler.java b/core/src/main/java/com/mapzen/android/search/MapzenSearchHttpHandler.java new file mode 100644 index 00000000..6d472cfe --- /dev/null +++ b/core/src/main/java/com/mapzen/android/search/MapzenSearchHttpHandler.java @@ -0,0 +1,75 @@ +package com.mapzen.android.search; + +import com.mapzen.android.core.GenericHttpHandler; +import com.mapzen.android.core.MapzenManager; +import com.mapzen.pelias.PeliasRequestHandler; + +import java.util.HashMap; +import java.util.Map; + +/** + * Base class for HTTP requests made by {@link MapzenSearch}. + */ +public abstract class MapzenSearchHttpHandler implements GenericHttpHandler { + + private SearchRequestHandler searchHandler; + + /** + * Public constructor. + */ + public MapzenSearchHttpHandler() { + searchHandler = new SearchRequestHandler(); + } + + /** + * Returns the internal handler. + * @return + */ + SearchRequestHandler searchHandler() { + return searchHandler; + } + + /** + * Request handler in charge of appending the api key and user agent for {@link MapzenSearch} + * requests. + */ + class SearchRequestHandler implements PeliasRequestHandler { + + private String apiKey; + + /** + * Set the api key to be used for {@link MapzenSearch} requests. + * @param apiKey + */ + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + /** + * Get the api key used for {@link MapzenSearch} requests. + */ + public String getApiKey() { + return apiKey; + } + + @Override public Map headersForRequest() { + Map headers = new HashMap<>(); + headers.put(HEADER_USER_AGENT, USER_AGENT); + Map customHeaders = MapzenSearchHttpHandler.this.headersForRequest(); + if (customHeaders != null) { + headers.putAll(customHeaders); + } + return headers; + } + + @Override public Map queryParamsForRequest() { + HashMap params = new HashMap<>(); + params.put(MapzenManager.API_KEY_PARAM_NAME, apiKey); + Map customParams = MapzenSearchHttpHandler.this.queryParamsForRequest(); + if (customParams != null) { + params.putAll(customParams); + } + return params; + } + } +} diff --git a/core/src/main/java/com/mapzen/android/search/SearchInitializer.java b/core/src/main/java/com/mapzen/android/search/SearchInitializer.java index b0e99df5..4f3404f0 100644 --- a/core/src/main/java/com/mapzen/android/search/SearchInitializer.java +++ b/core/src/main/java/com/mapzen/android/search/SearchInitializer.java @@ -4,15 +4,25 @@ import android.content.Context; +import java.util.Map; + /** * Handles setting the api key and a request handler for given {@link MapzenSearch} objects. */ public class SearchInitializer { - private SearchRequestHandler requestHandler = new SearchRequestHandler(); + private MapzenSearchHttpHandler requestHandler = new MapzenSearchHttpHandler() { + @Override public Map queryParamsForRequest() { + return null; + } + + @Override public Map headersForRequest() { + return null; + } + }; /** * Initialize the {@link MapzenSearch}'s api key from mapzen.xml and set it's - * {@link SearchRequestHandler}. + * {@link MapzenSearchHttpHandler}. * @param search * @param context */ @@ -22,20 +32,20 @@ public void initSearch(MapzenSearch search, Context context) { /** * Initialize the {@link MapzenSearch}'s api key in code and set it's - * {@link SearchRequestHandler}. + * {@link MapzenSearchHttpHandler}. * @param search * @param apiKey */ public void initSearch(MapzenSearch search, String apiKey) { - requestHandler.setApiKey(apiKey); - search.getPelias().setRequestHandler(requestHandler); + requestHandler.searchHandler().setApiKey(apiKey); + search.getPelias().setRequestHandler(requestHandler.searchHandler()); } /** * Returns the request handler. * @return */ - public SearchRequestHandler getRequestHandler() { + public MapzenSearchHttpHandler getRequestHandler() { return requestHandler; } } diff --git a/core/src/main/java/com/mapzen/android/search/SearchRequestHandler.java b/core/src/main/java/com/mapzen/android/search/SearchRequestHandler.java deleted file mode 100644 index e31018c2..00000000 --- a/core/src/main/java/com/mapzen/android/search/SearchRequestHandler.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.mapzen.android.search; - -import com.mapzen.android.core.MapzenManager; -import com.mapzen.pelias.PeliasRequestHandler; - -import java.util.HashMap; -import java.util.Map; - -/** - * Request handler in charge of appending the api key for {@link MapzenSearch} requests. - */ -public class SearchRequestHandler implements PeliasRequestHandler { - - private String apiKey; - - /** - * Set the api key to be used for {@link MapzenSearch} requests. - * @param apiKey - */ - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - /** - * Get the api key used for {@link MapzenSearch} requests. - */ - public String getApiKey() { - return apiKey; - } - - @Override public Map headersForRequest() { - return null; - } - - @Override public Map queryParamsForRequest() { - HashMap params = new HashMap<>(); - params.put(MapzenManager.API_KEY_PARAM_NAME, apiKey); - return params; - } -} diff --git a/core/src/test/java/com/mapzen/android/core/TestVals.java b/core/src/test/java/com/mapzen/android/core/TestVals.java new file mode 100644 index 00000000..011812e9 --- /dev/null +++ b/core/src/test/java/com/mapzen/android/core/TestVals.java @@ -0,0 +1,21 @@ +package com.mapzen.android.core; + +import java.util.HashMap; +import java.util.Map; + +public class TestVals { + + public static Map testParams() { + Map map = new HashMap<>(); + map.put("param1", "val1"); + map.put("param2", "val2"); + return map; + } + + public static Map testHeaders() { + Map map = new HashMap<>(); + map.put("header1", "val1"); + map.put("header2", "val2"); + return map; + } +} diff --git a/core/src/test/java/com/mapzen/android/graphics/MapInitializerTest.java b/core/src/test/java/com/mapzen/android/graphics/MapInitializerTest.java index 8495c812..d5cdc20e 100644 --- a/core/src/test/java/com/mapzen/android/graphics/MapInitializerTest.java +++ b/core/src/test/java/com/mapzen/android/graphics/MapInitializerTest.java @@ -40,7 +40,7 @@ public class MapInitializerTest { @Before public void setUp() throws Exception { CoreDI.init(getMockContext()); - mapInitializer = new MapInitializer(mock(Context.class), mock(TileHttpHandler.class), + mapInitializer = new MapInitializer(mock(Context.class), mock(MapzenMapHttpHandler.class), new MapDataManager(), new MapStateManager(), new SceneUpdateManager()); } diff --git a/core/src/test/java/com/mapzen/android/graphics/MapzenMapHttpHandlerTest.java b/core/src/test/java/com/mapzen/android/graphics/MapzenMapHttpHandlerTest.java new file mode 100644 index 00000000..e3c005d3 --- /dev/null +++ b/core/src/test/java/com/mapzen/android/graphics/MapzenMapHttpHandlerTest.java @@ -0,0 +1,49 @@ +package com.mapzen.android.graphics; + +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static com.mapzen.android.core.GenericHttpHandler.HEADER_USER_AGENT; +import static com.mapzen.android.core.GenericHttpHandler.USER_AGENT; +import static com.mapzen.android.core.TestVals.testHeaders; +import static com.mapzen.android.core.TestVals.testParams; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class MapzenMapHttpHandlerTest { + + MapzenMapHttpHandler handler; + MapzenMapHttpHandler.RequestEnqueuer enqueuer; + + @Before public void setup() throws Exception { + handler = new MapzenMapHttpHandler() { + @Override public Map queryParamsForRequest() { + return testParams(); + } + + @Override public Map headersForRequest() { + return testHeaders(); + } + }; + enqueuer = mock(MapzenMapHttpHandler.RequestEnqueuer.class); + handler.requestEnqueuer = enqueuer; + } + + @Test public void shouldHaveCorrectUrlAndHeaders() throws Exception { + handler.httpHandler().onRequest("http://mapzen.com?key=test&", null); + Map userAgentMap = new HashMap(); + userAgentMap.put(HEADER_USER_AGENT, USER_AGENT); + String url = "http://mapzen.com?key=test¶m1=val1¶m2=val2&"; + Map headers = testHeaders(); + headers.put(HEADER_USER_AGENT, USER_AGENT); + verify(enqueuer).enqueueRequest(any(OkHttpClient.class), any(Callback.class), eq(url), + eq(headers)); + } +} diff --git a/core/src/test/java/com/mapzen/android/graphics/TileHttpHandlerTest.java b/core/src/test/java/com/mapzen/android/graphics/TileHttpHandlerTest.java deleted file mode 100644 index 478873b3..00000000 --- a/core/src/test/java/com/mapzen/android/graphics/TileHttpHandlerTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.mapzen.android.graphics; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class TileHttpHandlerTest { - private TileHttpHandler tileHttpHandler = new TileHttpHandler(); - - @Test public void shouldNotBeNull() throws Exception { - assertThat(tileHttpHandler).isNotNull(); - } -} diff --git a/core/src/test/java/com/mapzen/android/search/MapzenSearchHttpHandlerTest.java b/core/src/test/java/com/mapzen/android/search/MapzenSearchHttpHandlerTest.java new file mode 100644 index 00000000..0ffe9b27 --- /dev/null +++ b/core/src/test/java/com/mapzen/android/search/MapzenSearchHttpHandlerTest.java @@ -0,0 +1,66 @@ +package com.mapzen.android.search; + +import com.mapzen.android.core.TestVals; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static com.mapzen.android.core.GenericHttpHandler.HEADER_USER_AGENT; +import static com.mapzen.android.core.GenericHttpHandler.USER_AGENT; +import static com.mapzen.android.core.MapzenManager.API_KEY_PARAM_NAME; +import static org.assertj.core.api.Assertions.assertThat; + +public class MapzenSearchHttpHandlerTest { + + Map testParams; + Map testHeaders; + + MapzenSearchHttpHandler handler = new MapzenSearchHttpHandler() { + @Override public Map queryParamsForRequest() { + return testParams; + } + + @Override public Map headersForRequest() { + return testHeaders; + } + }; + + @Test public void setApiKey_shouldSetKey() { + handler.searchHandler().setApiKey("TEST_KEY"); + assertThat(handler.searchHandler().getApiKey()).isEqualTo("TEST_KEY"); + } + + @Test public void queryParamsForRequest_shouldReturnApiKey() { + handler.searchHandler().setApiKey("TEST_KEY"); + Map expected = new HashMap(); + expected.put(API_KEY_PARAM_NAME, "TEST_KEY"); + assertThat(handler.searchHandler().queryParamsForRequest()).isEqualTo(expected); + } + + @Test public void headersForRequest_shouldIncludeUserAgent() { + Map headers = handler.searchHandler().headersForRequest(); + Map expected = new HashMap(); + expected.put(HEADER_USER_AGENT, USER_AGENT); + assertThat(headers).isEqualTo(expected); + } + + @Test public void queryParamsForRequest_shouldReturnAllParams() { + testParams = TestVals.testParams(); + handler.searchHandler().setApiKey("TEST_KEY"); + Map expected = new HashMap(); + expected.put(API_KEY_PARAM_NAME, "TEST_KEY"); + expected.putAll(testParams); + assertThat(handler.searchHandler().queryParamsForRequest()).isEqualTo(expected); + } + + @Test public void headersForRequest_shouldReturnAllHeaders() { + testHeaders = TestVals.testHeaders(); + Map headers = handler.searchHandler().headersForRequest(); + Map expected = new HashMap(); + expected.put(HEADER_USER_AGENT, USER_AGENT); + expected.putAll(testHeaders); + assertThat(headers).isEqualTo(expected); + } +} diff --git a/core/src/test/java/com/mapzen/android/search/MapzenSearchTest.java b/core/src/test/java/com/mapzen/android/search/MapzenSearchTest.java index f380c3d0..4ea19cc1 100644 --- a/core/src/test/java/com/mapzen/android/search/MapzenSearchTest.java +++ b/core/src/test/java/com/mapzen/android/search/MapzenSearchTest.java @@ -40,7 +40,7 @@ public class MapzenSearchTest { MapzenManager.instance(context).setApiKey("fake-mapzen-api-key"); MapzenSearch mzSearch = new MapzenSearch(context); assertThat(mzSearch.getPelias()).isNotNull(); - assertThat(mzSearch.searchInitializer.getRequestHandler().getApiKey()) + assertThat(mzSearch.searchInitializer.getRequestHandler().searchHandler().getApiKey()) .isEqualTo("fake-mapzen-api-key"); MapzenManager.instance(context).setApiKey(null); } diff --git a/core/src/test/java/com/mapzen/android/search/SearchInitializerTest.java b/core/src/test/java/com/mapzen/android/search/SearchInitializerTest.java index c56244d1..db8d289b 100644 --- a/core/src/test/java/com/mapzen/android/search/SearchInitializerTest.java +++ b/core/src/test/java/com/mapzen/android/search/SearchInitializerTest.java @@ -25,6 +25,7 @@ public class SearchInitializerTest { @Test public void initSearch_shouldSetApiKey() { searchInitializer.initSearch(search, "TEST_API_KEY"); - assertThat(searchInitializer.getRequestHandler().getApiKey()).isEqualTo("TEST_API_KEY"); + assertThat(searchInitializer.getRequestHandler().searchHandler().getApiKey()).isEqualTo( + "TEST_API_KEY"); } } diff --git a/core/src/test/java/com/mapzen/android/search/SearchRequestHandlerTest.java b/core/src/test/java/com/mapzen/android/search/SearchRequestHandlerTest.java deleted file mode 100644 index 403b3134..00000000 --- a/core/src/test/java/com/mapzen/android/search/SearchRequestHandlerTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.mapzen.android.search; - -import com.mapzen.android.core.MapzenManager; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class SearchRequestHandlerTest { - - SearchRequestHandler adapter = new SearchRequestHandler(); - - @Test public void setApiKey_shouldSetKey() { - adapter.setApiKey("TEST_KEY"); - assertThat(adapter.getApiKey()).isEqualTo("TEST_KEY"); - } - - @Test public void queryParamsForRequest_shouldReturnApiKey() { - adapter.setApiKey("TEST_KEY"); - assertThat(adapter.queryParamsForRequest().get(MapzenManager.API_KEY_PARAM_NAME)) - .isEqualTo("TEST_KEY"); - } -} diff --git a/mapzen-android-sdk/src/main/java/com/mapzen/android/core/AndroidModule.java b/mapzen-android-sdk/src/main/java/com/mapzen/android/core/AndroidModule.java index 8ee421db..ed644e2f 100644 --- a/mapzen-android-sdk/src/main/java/com/mapzen/android/core/AndroidModule.java +++ b/mapzen-android-sdk/src/main/java/com/mapzen/android/core/AndroidModule.java @@ -1,6 +1,6 @@ package com.mapzen.android.core; -import com.mapzen.android.routing.TurnByTurnHttpHandler; +import com.mapzen.android.routing.RouterInitializer; import android.content.Context; import android.content.res.Resources; @@ -43,9 +43,7 @@ /** * Provides HTTP handler to append API key to outgoing turn-by-turn requests. */ - @Provides @Singleton public TurnByTurnHttpHandler provideTurnByTurnHttpHandler() { - final TurnByTurnHttpHandler handler = new TurnByTurnHttpHandler(); - handler.setApiKey(MapzenManager.instance(context).getApiKey()); - return handler; + @Provides @Singleton public RouterInitializer provideRouterInitializer() { + return new RouterInitializer(); } } diff --git a/mapzen-android-sdk/src/main/java/com/mapzen/android/routing/MapzenRouter.java b/mapzen-android-sdk/src/main/java/com/mapzen/android/routing/MapzenRouter.java index 2b92f747..c1b60d86 100644 --- a/mapzen-android-sdk/src/main/java/com/mapzen/android/routing/MapzenRouter.java +++ b/mapzen-android-sdk/src/main/java/com/mapzen/android/routing/MapzenRouter.java @@ -19,14 +19,14 @@ public class MapzenRouter { private Router internalRouter = new ValhallaRouter(); - @Inject TurnByTurnHttpHandler httpHandler; + @Inject RouterInitializer routerInitializer; /** * Creates a new {@link MapzenRouter} with api key set from mapzen.xml. */ public MapzenRouter(Context context) { initDI(context); - internalRouter.setHttpHandler(httpHandler); + routerInitializer.initRouter(this, context); } private void initDI(Context context) { diff --git a/mapzen-android-sdk/src/main/java/com/mapzen/android/routing/MapzenRouterHttpHandler.java b/mapzen-android-sdk/src/main/java/com/mapzen/android/routing/MapzenRouterHttpHandler.java new file mode 100644 index 00000000..a160819b --- /dev/null +++ b/mapzen-android-sdk/src/main/java/com/mapzen/android/routing/MapzenRouterHttpHandler.java @@ -0,0 +1,135 @@ +package com.mapzen.android.routing; + +import com.mapzen.android.core.GenericHttpHandler; +import com.mapzen.valhalla.HttpHandler; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import okhttp3.HttpUrl; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.logging.HttpLoggingInterceptor; + +/** + * Base class for HTTP requests made by {@link MapzenRouter}. + */ +public abstract class MapzenRouterHttpHandler implements GenericHttpHandler { + + private TurnByTurnHttpHandler handler; + ChainProceder chainProceder = new ChainProceder(); + + /** + * Construct handler with default url and log levels. + */ + public MapzenRouterHttpHandler() { + handler = new TurnByTurnHttpHandler(HttpLoggingInterceptor.Level.BODY); + } + + /** + * Returns the internal handler. + * @return + */ + TurnByTurnHttpHandler turnByTurnHandler() { + return handler; + } + + /** + * Handles appending api keys for all turn-by-turn requests. + */ + class TurnByTurnHttpHandler extends HttpHandler { + + static final String NAME_API_KEY = "api_key"; + + private String apiKey; + + /** + * Construct handler with default url and log levels. + */ + public TurnByTurnHttpHandler() { + configure(DEFAULT_URL, DEFAULT_LOG_LEVEL); + } + + /** + * Construct handler with url and default log levels. + */ + public TurnByTurnHttpHandler(String endpoint) { + configure(endpoint, DEFAULT_LOG_LEVEL); + } + + /** + * Construct handler with log levels and default url. + */ + public TurnByTurnHttpHandler(HttpLoggingInterceptor.Level logLevel) { + configure(DEFAULT_URL, logLevel); + } + + /** + * Construct handler with url and log levels. + */ + public TurnByTurnHttpHandler(String endpoint, HttpLoggingInterceptor.Level logLevel) { + configure(endpoint, logLevel); + } + + /** + * Set the api key to be sent with every request. + */ + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + @Override protected Response onRequest(Interceptor.Chain chain) throws IOException { + Map params = new HashMap(); + params.put(NAME_API_KEY, apiKey); + Map customParams = queryParamsForRequest(); + if (customParams != null) { + params.putAll(customParams); + } + + Map headers = new HashMap(); + headers.put(HEADER_USER_AGENT, USER_AGENT); + Map customHeaders = headersForRequest(); + if (customHeaders != null) { + headers.putAll(customHeaders); + } + + return chainProceder.proceed(chain, params, headers); + } + } + + /** + * Class to handle creating requests to feed to the chain. + */ + class ChainProceder { + /** + * Creates a request given the parameters and headers and feeds it to the chain for execution. + * @param chain + * @param params + * @param headers + * @return + * @throws IOException + */ + Response proceed(Interceptor.Chain chain, Map params, + Map headers) throws IOException { + final HttpUrl.Builder urlBuilder = chain + .request() + .url() + .newBuilder(); + for (String param : params.keySet()) { + urlBuilder.addQueryParameter(param, params.get(param)); + } + + Request.Builder requestBuilder = chain + .request() + .newBuilder() + .url(urlBuilder.build()); + for (String header : headers.keySet()) { + requestBuilder.header(header, headers.get(header)); + } + + return chain.proceed(requestBuilder.build()); + } + } +} diff --git a/mapzen-android-sdk/src/main/java/com/mapzen/android/routing/RouterInitializer.java b/mapzen-android-sdk/src/main/java/com/mapzen/android/routing/RouterInitializer.java new file mode 100644 index 00000000..d2f07285 --- /dev/null +++ b/mapzen-android-sdk/src/main/java/com/mapzen/android/routing/RouterInitializer.java @@ -0,0 +1,51 @@ +package com.mapzen.android.routing; + +import com.mapzen.android.core.MapzenManager; + +import android.content.Context; + +import java.util.Map; + +/** + * Handles setting the api key and a request handler for given {@link MapzenRouter} objects. + */ +public class RouterInitializer { + final MapzenRouterHttpHandler requestHandler = new MapzenRouterHttpHandler() { + @Override public Map queryParamsForRequest() { + return null; + } + + @Override public Map headersForRequest() { + return null; + } + }; + + /** + * Initialize the {@link MapzenRouter}'s api key from mapzen.xml and set it's + * {@link MapzenRouterHttpHandler}. + * @param router + * @param context + */ + public void initRouter(MapzenRouter router, Context context) { + initRouter(router, MapzenManager.instance(context).getApiKey()); + } + + /** + * Initialize the {@link MapzenRouter}'s api key in code and set it's + * {@link MapzenRouterHttpHandler}. + * @param router + * @param apiKey + */ + public void initRouter(MapzenRouter router, String apiKey) { + requestHandler.turnByTurnHandler().setApiKey(apiKey); + router.getRouter().setHttpHandler(requestHandler.turnByTurnHandler()); + } + + /** + * Returns the request handler. + * @return + */ + public MapzenRouterHttpHandler getRequestHandler() { + return requestHandler; + } +} diff --git a/mapzen-android-sdk/src/main/java/com/mapzen/android/routing/TurnByTurnHttpHandler.java b/mapzen-android-sdk/src/main/java/com/mapzen/android/routing/TurnByTurnHttpHandler.java deleted file mode 100644 index ed423133..00000000 --- a/mapzen-android-sdk/src/main/java/com/mapzen/android/routing/TurnByTurnHttpHandler.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.mapzen.android.routing; - -import com.mapzen.valhalla.HttpHandler; - -import java.io.IOException; - -import okhttp3.HttpUrl; -import okhttp3.Interceptor; -import okhttp3.Response; -import okhttp3.logging.HttpLoggingInterceptor; - -/** - * Handles appending api keys for all turn-by-turn requests. - */ -public class TurnByTurnHttpHandler extends HttpHandler { - - private static final String NAME_API_KEY = "api_key"; - - private String apiKey; - - /** - * Construct handler with default url and log levels. - */ - public TurnByTurnHttpHandler() { - configure(DEFAULT_URL, DEFAULT_LOG_LEVEL); - } - - /** - * Construct handler with url and default log levels. - */ - public TurnByTurnHttpHandler(String endpoint) { - configure(endpoint, DEFAULT_LOG_LEVEL); - } - - /** - * Construct handler with log levels and default url. - */ - public TurnByTurnHttpHandler(HttpLoggingInterceptor.Level logLevel) { - configure(DEFAULT_URL, logLevel); - } - - /** - * Construct handler with url and log levels. - */ - public TurnByTurnHttpHandler(String endpoint, HttpLoggingInterceptor.Level logLevel) { - configure(endpoint, logLevel); - } - - /** - * Set the api key to be sent with every request. - */ - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - @Override protected Response onRequest(Interceptor.Chain chain) throws IOException { - final HttpUrl url = chain.request() - .url() - .newBuilder() - .addQueryParameter(NAME_API_KEY, apiKey) - .build(); - return chain.proceed(chain.request().newBuilder().url(url).build()); - } -} diff --git a/mapzen-android-sdk/src/test/java/com/mapzen/android/routing/MapzenRouterHttpHandlerTest.java b/mapzen-android-sdk/src/test/java/com/mapzen/android/routing/MapzenRouterHttpHandlerTest.java new file mode 100644 index 00000000..724f23ac --- /dev/null +++ b/mapzen-android-sdk/src/test/java/com/mapzen/android/routing/MapzenRouterHttpHandlerTest.java @@ -0,0 +1,67 @@ +package com.mapzen.android.routing; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static com.mapzen.android.core.GenericHttpHandler.HEADER_USER_AGENT; +import static com.mapzen.android.core.GenericHttpHandler.USER_AGENT; +import static com.mapzen.android.routing.MapzenRouterHttpHandler.TurnByTurnHttpHandler.NAME_API_KEY; +import okhttp3.Interceptor; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class MapzenRouterHttpHandlerTest { + + Map testParams; + Map testHeaders; + + MapzenRouterHttpHandler httpHandler = new MapzenRouterHttpHandler() { + @Override public Map queryParamsForRequest() { + return testParams; + } + + @Override public Map headersForRequest() { + return testHeaders; + } + }; + + @Test public void onRequest_shouldProceedWithDefaultParamsAndHeaders() throws Exception { + httpHandler.turnByTurnHandler().setApiKey("test-key"); + MapzenRouterHttpHandler.ChainProceder proceder = mock( + MapzenRouterHttpHandler.ChainProceder.class); + httpHandler.chainProceder = proceder; + Interceptor.Chain chain = mock(Interceptor.Chain.class); + httpHandler.turnByTurnHandler().onRequest(chain); + + Map params = new HashMap(); + params.put(NAME_API_KEY, "test-key"); + Map headers = new HashMap(); + headers.put(HEADER_USER_AGENT, USER_AGENT); + + verify(proceder).proceed(chain, params, headers); + } + + @Test public void onRequest_shouldProceedWithAllParamsAndHeaders() throws Exception { + httpHandler.turnByTurnHandler().setApiKey("test-key"); + MapzenRouterHttpHandler.ChainProceder proceder = mock( + MapzenRouterHttpHandler.ChainProceder.class); + httpHandler.chainProceder = proceder; + Interceptor.Chain chain = mock(Interceptor.Chain.class); + testParams = new HashMap(); + testParams.put("param", "value"); + testHeaders = new HashMap(); + testHeaders.put("header", "value"); + httpHandler.turnByTurnHandler().onRequest(chain); + + Map params = new HashMap(); + params.put(NAME_API_KEY, "test-key"); + params.putAll(testParams); + Map headers = new HashMap(); + headers.put(HEADER_USER_AGENT, USER_AGENT); + headers.putAll(testHeaders); + + verify(proceder).proceed(chain, params, headers); + } +} diff --git a/mapzen-android-sdk/src/test/java/com/mapzen/android/routing/RouterInitializerTest.java b/mapzen-android-sdk/src/test/java/com/mapzen/android/routing/RouterInitializerTest.java new file mode 100644 index 00000000..81caaf7b --- /dev/null +++ b/mapzen-android-sdk/src/test/java/com/mapzen/android/routing/RouterInitializerTest.java @@ -0,0 +1,56 @@ +package com.mapzen.android.routing; + +import com.mapzen.android.core.MapzenManager; +import com.mapzen.valhalla.Router; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static com.mapzen.android.TestHelper.getMockContext; +import static com.mapzen.android.core.GenericHttpHandler.HEADER_USER_AGENT; +import static com.mapzen.android.core.GenericHttpHandler.USER_AGENT; +import static com.mapzen.android.routing.MapzenRouterHttpHandler.TurnByTurnHttpHandler.NAME_API_KEY; +import okhttp3.Interceptor; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class RouterInitializerTest { + private RouterInitializer routerInitializer; + private MapzenRouter router; + + @Before public void setUp() throws Exception { + MapzenManager.instance(getMockContext()).setApiKey("fake-mapzen-api-key"); + routerInitializer = new RouterInitializer(); + router = mock(MapzenRouter.class); + when(router.getRouter()).thenReturn(mock(Router.class)); + } + + @After public void tearDown() throws Exception { + MapzenManager.instance(getMockContext()).setApiKey(null); + } + + @Test public void initRouter_shouldSetApiKey() throws Exception { + routerInitializer.initRouter(router, "TEST_API_KEY"); + MapzenRouterHttpHandler.ChainProceder proceder = mock( + MapzenRouterHttpHandler.ChainProceder.class); + routerInitializer.getRequestHandler().chainProceder = proceder; + Interceptor.Chain chain = mock(Interceptor.Chain.class); + routerInitializer.getRequestHandler().turnByTurnHandler().onRequest(chain); + Map params = new HashMap(); + params.put(NAME_API_KEY, "TEST_API_KEY"); + Map headers = new HashMap(); + headers.put(HEADER_USER_AGENT, USER_AGENT); + verify(proceder).proceed(chain, params, headers); + } + + @Test public void initRouter_shouldSetHttpHandler() throws Exception { + routerInitializer.initRouter(router, "TEST_API_KEY"); + verify(router.getRouter()).setHttpHandler( + routerInitializer.getRequestHandler().turnByTurnHandler()); + } +}