Improve POST handling, start adding tests, add LICENSE file
diff --git a/LICENSE b/LICENSE
index f927b76..861f99b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -203,8 +203,3 @@
     See the License for the specific language governing permissions and
     limitations under the License.
     -------------------------------------------------------------
-
-
-
-APPENDIX B: Additional licenses relevant to this product:
-    (none)
diff --git a/NOTICE b/NOTICE
index 01aca2a..8ff8dcf 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,5 +1,5 @@
-Apache DataSketches Server
-Copyright 2020 - The Apache Software Foundation
+Apache DataSketches-server
+Copyright 2021 The Apache Software Foundation
 
 This product includes software developed at
 The Apache Software Foundation (http://www.apache.org/).
diff --git a/src/main/java/org/apache/datasketches/server/SketchConstants.java b/src/main/java/org/apache/datasketches/server/SketchConstants.java
index 742dfc9..1e4059b 100644
--- a/src/main/java/org/apache/datasketches/server/SketchConstants.java
+++ b/src/main/java/org/apache/datasketches/server/SketchConstants.java
@@ -21,12 +21,12 @@
 
 public final class SketchConstants {
   // API call paths, relative to root
-  public static final String UPDATE_PATH = "/update";
-  public static final String SERIALIZE_PATH = "/serialize";
-  public static final String STATUS_PATH = "/status";
-  public static final String QUERY_PATH = "/query";
-  public static final String MERGE_PATH = "/merge";
-  public static final String RESET_PATH = "/reset";
+  public static final String UPDATE_PATH = "update";
+  public static final String SERIALIZE_PATH = "serialize";
+  public static final String STATUS_PATH = "status";
+  public static final String QUERY_PATH = "query";
+  public static final String MERGE_PATH = "merge";
+  public static final String RESET_PATH = "reset";
 
   // JSON Query/Update/Merge Field Names
   public static final String QUERY_NAME_FIELD = "name";
diff --git a/src/main/java/org/apache/datasketches/server/SketchServer.java b/src/main/java/org/apache/datasketches/server/SketchServer.java
index b70dca6..e0a8a0e 100644
--- a/src/main/java/org/apache/datasketches/server/SketchServer.java
+++ b/src/main/java/org/apache/datasketches/server/SketchServer.java
@@ -50,30 +50,42 @@
 
   // defines paths and registers the relevant handlers
   private void createServer() {
-    server = new Server(config.getPort());
+    server = new Server();
+
+    // configure port
+    final ServerConnector http = new ServerConnector(server);
+    http.setHost("localhost");
+    http.setPort(config.getPort());
+    server.addConnector(http);
 
     // Error page unless you have a correct URL
     final ContextHandler contextRoot = new ContextHandler("/");
-    contextRoot.setContextPath("/");
     contextRoot.setErrorHandler(new ErrorHandler());
 
-    final ContextHandler contextStatus = new ContextHandler(STATUS_PATH);
+    // Add specific handlers
+    final ContextHandler contextStatus = new ContextHandler("/" + STATUS_PATH);
     contextStatus.setHandler(new StatusHandler(sketches));
+    contextStatus.setAllowNullPathInfo(true);
 
-    final ContextHandler contextSerialize = new ContextHandler(SERIALIZE_PATH);
+    final ContextHandler contextSerialize = new ContextHandler("/" + SERIALIZE_PATH);
     contextSerialize.setHandler(new SerializationHandler(sketches));
+    contextSerialize.setAllowNullPathInfo(true);
 
-    final ContextHandler contextUpdate = new ContextHandler(UPDATE_PATH);
+    final ContextHandler contextUpdate = new ContextHandler("/" + UPDATE_PATH);
     contextUpdate.setHandler(new UpdateHandler(sketches));
+    contextUpdate.setAllowNullPathInfo(true);
 
-    final ContextHandler contextMerge = new ContextHandler(MERGE_PATH);
+    final ContextHandler contextMerge = new ContextHandler("/" + MERGE_PATH);
     contextMerge.setHandler(new MergeHandler(sketches));
+    contextMerge.setAllowNullPathInfo(true);
 
-    final ContextHandler contextQuery = new ContextHandler(QUERY_PATH);
+    final ContextHandler contextQuery = new ContextHandler("/" + QUERY_PATH);
     contextQuery.setHandler(new DataQueryHandler(sketches));
+    contextQuery.setAllowNullPathInfo(true);
 
-    final ContextHandler contextReset = new ContextHandler(RESET_PATH);
+    final ContextHandler contextReset = new ContextHandler("/" + RESET_PATH);
     contextReset.setHandler(new ResetHandler(sketches));
+    contextReset.setAllowNullPathInfo(true);
 
     final ContextHandlerCollection contexts =
         new ContextHandlerCollection(contextRoot,
@@ -117,6 +129,34 @@
     return -1;
   }
 
+  /**
+   * Returns the server's running status
+   * @return True for a running server, otherwise false
+   */
+  public boolean isRunning() {
+    return server != null && server.isRunning();
+  }
+
+  /**
+   * Stops the server from running. Cannot be be restarted without creating new sketches.
+   * @throws Exception Upon underlying server throwing an Exception
+   */
+  public void stop() throws Exception {
+    if (server != null) {
+      server.stop();
+      server.isStarted();
+    }
+  }
+
+  /**
+   * Package-private test method to get a specific SketchEntry
+   * @param name The name of the desired sketch
+   * @return The SketchEntry containing the sketch and type info
+   */
+  SketchStorage.SketchEntry getSketch(@NonNull final String name) {
+    return sketches.getSketch(name);
+  }
+
   public static void main(final String[] args) throws Exception {
     if (args.length < 1) {
       System.err.println("Usage: SketchServer <config_file>");
diff --git a/src/test/java/org/apache/datasketches/server/ServerTestBase.java b/src/test/java/org/apache/datasketches/server/ServerTestBase.java
new file mode 100644
index 0000000..8c0d7f5
--- /dev/null
+++ b/src/test/java/org/apache/datasketches/server/ServerTestBase.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.datasketches.server;
+
+import static org.testng.Assert.fail;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Objects;
+import java.io.DataOutputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+public class ServerTestBase {
+  final static String RESPONSE_FIELD = "response";
+
+  SketchServer server_ = null;
+  String serverUri_ = null;
+
+  @BeforeClass
+  public void launchServer() {
+    final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+    try {
+      server_ = new SketchServer(Objects.requireNonNull(classLoader.getResource("test_config.json")).getFile());
+      server_.start();
+      serverUri_ = server_.getURI();
+    } catch (final Exception e) {
+      fail();
+    }
+  }
+
+  @AfterClass
+  public void shutdownServer() {
+    if (server_ != null) {
+      try {
+        server_.stop();
+      } catch (final Exception e) {
+        fail();
+      }
+    }
+  }
+
+  int postData(@NonNull final String path,
+               @NonNull final JsonObject data,
+               @NonNull final JsonObject response) {
+    HttpURLConnection http = null;
+    int status = -1;
+
+    try {
+      // set up the POST
+      final URL url = new URL(serverUri_ + path);
+      http = (HttpURLConnection) url.openConnection();
+      http.setDoInput(true);
+      http.setDoOutput(true);
+      http.setRequestMethod("POST");
+      http.setRequestProperty("Content-Type", "application/json");
+      http.setRequestProperty("Accept", "application/json");
+
+      final byte[] jsonBytes = data.toString().getBytes(StandardCharsets.UTF_8);
+      http.setRequestProperty("Content-length", Integer.toString(jsonBytes.length));
+
+      // write JSON data to to stream
+      try (final DataOutputStream os = new DataOutputStream(http.getOutputStream())) {
+        os.write(jsonBytes);
+      }
+
+      status = http.getResponseCode();
+      if (status == HttpServletResponse.SC_OK) {
+        // read response, if any, and put into a JSON element
+        try (final InputStreamReader isr = new InputStreamReader(http.getInputStream())) {
+          response.add(RESPONSE_FIELD, JsonParser.parseReader(isr));
+        }
+      }
+    } catch (final IOException e) {
+      fail();
+    } finally {
+      if (http != null)
+        http.disconnect();
+    }
+
+    return status;
+  }
+
+  int getData(@NonNull final String path,
+              @NonNull final JsonObject data,
+              @NonNull final JsonObject response) {
+    HttpURLConnection http = null;
+    int status = -1;
+
+    try {
+      // set up the POST
+      final URL url = new URL(serverUri_ + path + "?" + data);
+      http = (HttpURLConnection) url.openConnection();
+      http.setDoInput(true);
+      http.setRequestProperty("Content-Type", "application/json");
+      http.connect();
+
+      status = http.getResponseCode();
+      if (status == HttpServletResponse.SC_OK) {
+        // read response, if any, and put into a JSON element
+        try (final InputStreamReader isr = new InputStreamReader(http.getInputStream())) {
+          response.add(RESPONSE_FIELD, JsonParser.parseReader(isr));
+        }
+      }
+    } catch (final IOException e) {
+      fail();
+    } finally {
+      if (http != null)
+        http.disconnect();
+    }
+
+    return status;
+  }
+}
diff --git a/src/test/java/org/apache/datasketches/server/SketchServerConfigTest.java b/src/test/java/org/apache/datasketches/server/SketchServerConfigTest.java
index 23dac17..861d58a 100644
--- a/src/test/java/org/apache/datasketches/server/SketchServerConfigTest.java
+++ b/src/test/java/org/apache/datasketches/server/SketchServerConfigTest.java
@@ -45,8 +45,8 @@
     final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
     try {
       final SketchServerConfig serverConf =
-          new SketchServerConfig(Objects.requireNonNull(classLoader.getResource("test_config.json")).getFile());
-      assertEquals(serverConf.getSketchList().size(), 15);
+          new SketchServerConfig(Objects.requireNonNull(classLoader.getResource("config_with_port.json")).getFile());
+      assertEquals(serverConf.getSketchList().size(), 2);
       assertEquals(serverConf.getPort(), 8080);
     } catch (final IOException e) {
       fail();
diff --git a/src/test/java/org/apache/datasketches/server/SketchServerTest.java b/src/test/java/org/apache/datasketches/server/SketchServerTest.java
index 757c855..61a6d04 100644
--- a/src/test/java/org/apache/datasketches/server/SketchServerTest.java
+++ b/src/test/java/org/apache/datasketches/server/SketchServerTest.java
@@ -20,6 +20,7 @@
 package org.apache.datasketches.server;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
@@ -43,17 +44,23 @@
 
     // check that port and URI are invalid before starting the server
     assertNotNull(server);
+    assertFalse(server.isRunning());
     assertNull(server.getURI());
     assertEquals(server.getPort(), -1);
     try {
       server.start();
+      assertTrue(server.isRunning());
+
+      // add the few tests in the try block for code simplicity
+      assertEquals(server.getPort(), 8080);
+      // initial testing suggests it's just using the host's IP address so just checking that the port
+      // is working correctly
+      assertTrue(server.getURI().endsWith(":" + server.getPort() + "/"));
+
+      server.stop();
+      assertFalse(server.isRunning());
     } catch (final Exception e) {
       fail();
     }
-
-    assertEquals(server.getPort(), 8080);
-    // initial testing suggests it's just using the host's IP address so just checking that the port
-    // is working correctly
-    assertTrue(server.getURI().endsWith(":" + server.getPort() + "/"));
   }
 }
diff --git a/src/test/java/org/apache/datasketches/server/UpdateHandlerTest.java b/src/test/java/org/apache/datasketches/server/UpdateHandlerTest.java
new file mode 100644
index 0000000..49969bb
--- /dev/null
+++ b/src/test/java/org/apache/datasketches/server/UpdateHandlerTest.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.datasketches.server;
+
+import static org.apache.datasketches.server.SketchConstants.QUERY_PAIR_ITEM_FIELD;
+import static org.apache.datasketches.server.SketchConstants.QUERY_PAIR_WEIGHT_FIELD;
+import static org.apache.datasketches.server.SketchConstants.UPDATE_PATH;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.datasketches.cpc.CpcSketch;
+import org.testng.annotations.Test;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+
+public class UpdateHandlerTest extends ServerTestBase {
+  /* The tests here are going to be structured very similarly. It might be possible
+   * to find a common framework and reduce the amount o repetition? But not clear with
+   * type erasure as opposed to C++-style templates.
+   */
+
+  @Test
+  public void cpcUpdate() {
+    final JsonObject response = new JsonObject();
+    final String sketchName = "cpcOfNumbers";
+    final int nPoints = 1000;
+
+    // testing using both GET and POST
+    int status;
+
+    JsonObject request = new JsonObject();
+    JsonArray data = new JsonArray();
+    for (int i = 0; i < nPoints; ++i)
+      data.add(i);
+    request.add(sketchName, data);
+    status = postData(UPDATE_PATH, request, response);
+    assertEquals(status, HttpServletResponse.SC_OK);
+
+    request = new JsonObject();
+    data = new JsonArray();
+    for (int i = nPoints; i < 2 * nPoints; ++i)
+      data.add(i);
+    request.add(sketchName, data);
+    status = getData(UPDATE_PATH, request, response);
+    assertEquals(status, HttpServletResponse.SC_OK);
+
+    final JsonElement element = response.get(RESPONSE_FIELD);
+    assertTrue(element.isJsonNull());
+
+    final SketchStorage.SketchEntry entry = server_.getSketch(sketchName);
+    final CpcSketch sk = (CpcSketch) entry.sketch;
+    assertEquals(sk.getEstimate(), 2 * nPoints, 2 * nPoints * 1e-2);
+  }
+
+  @Test
+  @SuppressWarnings("unchecked")
+  public void fiUpdate() {
+    final JsonObject response = new JsonObject();
+    final String sketchName = "topItems";
+
+    // single item
+    JsonObject request = new JsonObject();
+    request.addProperty(sketchName, "item1");
+    assertEquals(postData(UPDATE_PATH, request, response), HttpServletResponse.SC_OK);
+
+    // item with weight
+    request = new JsonObject();
+    JsonObject data = new JsonObject();
+    data.addProperty(QUERY_PAIR_ITEM_FIELD, "item2");
+    data.addProperty(QUERY_PAIR_WEIGHT_FIELD, 5);
+    request.add(sketchName, data);
+    assertEquals(postData(UPDATE_PATH, request, response), HttpServletResponse.SC_OK);
+
+    // array of items with and without weights
+    request = new JsonObject();
+    final JsonArray dataArray = new JsonArray();
+    dataArray.add("item1"); // increases count to 2
+    data = new JsonObject();
+    data.addProperty(QUERY_PAIR_ITEM_FIELD, "item3");
+    data.addProperty(QUERY_PAIR_WEIGHT_FIELD, 10);
+    dataArray.add(data);
+    request.add(sketchName, dataArray);
+    assertEquals(postData(UPDATE_PATH, request, response), HttpServletResponse.SC_OK);
+
+    final JsonElement element = response.get(RESPONSE_FIELD);
+    assertTrue(element.isJsonNull());
+
+    final SketchStorage.SketchEntry entry = server_.getSketch(sketchName);
+    final org.apache.datasketches.frequencies.ItemsSketch<String> sk = (org.apache.datasketches.frequencies.ItemsSketch<String>) entry.sketch;
+    assertEquals(sk.getEstimate("item1"), 2);
+    assertEquals(sk.getEstimate("item2"), 5);
+    assertEquals(sk.getEstimate("item3"), 10);
+  }
+
+  @Test
+  public void hllUpdate() {
+    // update multiple sketches from an array
+  }
+
+  @Test
+  public void kllUpdate() {
+
+  }
+
+  @Test
+  public void thetaUpdate() {
+
+  }
+
+  @Test
+  public void reservoirUpdate() {
+
+  }
+
+  @Test
+  public void voUpdate() {
+
+  }
+
+}
diff --git a/src/test/resources/config_with_port.json b/src/test/resources/config_with_port.json
new file mode 100644
index 0000000..f148049
--- /dev/null
+++ b/src/test/resources/config_with_port.json
@@ -0,0 +1,15 @@
+{
+  "port": 8080,
+  "sketches_A": [
+    { "name": "cpcOfNumbers",
+      "k": 12,
+      "type": "long",
+      "family": "cpc"
+    },
+    { "name": "cpcOfStrings",
+      "k": 14,
+      "type": "string",
+      "family": "cpc"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/test/resources/test_config.json b/src/test/resources/test_config.json
index 172086e..a685c17 100644
--- a/src/test/resources/test_config.json
+++ b/src/test/resources/test_config.json
@@ -1,5 +1,5 @@
 {
-    "port": 8080,
+	"port": 0,
     "sketches_A": [
 	{ "name": "cpcOfNumbers",
 	  "k": 12,
@@ -10,7 +10,7 @@
 	  "k": 14,
 	  "type": "string",
 	  "family": "cpc"
-	}	
+	}
     ],
     "set1": {
 	"family": "hll",