Merge commit '6312edf' from 'stabilization-f69489' into 'master'

Change-Id: I6f2daa3ab112d8cb9210ed94db812ac1743bce58
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ApiServlet.java
index ee3eb9c..ea6e616 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ApiServlet.java
@@ -60,7 +60,6 @@
 import org.apache.hyracks.http.server.StaticResourceServlet;
 import org.apache.hyracks.http.server.utils.HttpUtil;
 import org.apache.hyracks.http.server.utils.HttpUtil.ContentType;
-import org.apache.hyracks.http.server.utils.HttpUtil.Encoding;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -96,6 +95,13 @@
                 ? aqlCompilationProvider : sqlppCompilationProvider;
         IParserFactory parserFactory = compilationProvider.getParserFactory();
 
+        try {
+            HttpUtil.setContentType(response, ContentType.TEXT_HTML, request);
+        } catch (IOException e) {
+            LOGGER.log(Level.WARN, "Failure setting content type", e);
+            response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
+            return;
+        }
         // Output format.
         PrintWriter out = response.writer();
         OutputFormat format;
@@ -126,14 +132,7 @@
         String printOptimizedLogicalPlanParam = request.getParameter("print-optimized-logical-plan");
         String printJob = request.getParameter("print-job");
         String executeQuery = request.getParameter("execute-query");
-        try {
-            response.setStatus(HttpResponseStatus.OK);
-            HttpUtil.setContentType(response, ContentType.TEXT_HTML, Encoding.UTF8);
-        } catch (IOException e) {
-            LOGGER.log(Level.WARN, "Failure setting content type", e);
-            response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
-            return;
-        }
+        response.setStatus(HttpResponseStatus.OK);
         try {
             IHyracksClientConnection hcc = (IHyracksClientConnection) ctx.get(HYRACKS_CONNECTION_ATTR);
             IResultSet resultSet = ServletUtil.getResultSet(hcc, appCtx, ctx);
@@ -175,7 +174,7 @@
         response.setStatus(HttpResponseStatus.OK);
         if ("/".equals(requestURI)) {
             try {
-                HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_HTML, HttpUtil.Encoding.UTF8);
+                HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_HTML, request);
             } catch (IOException e) {
                 LOGGER.log(Level.WARN, "Failure setting content type", e);
                 response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
@@ -208,7 +207,6 @@
         } catch (IOException e) {
             LOGGER.log(Level.WARN, "Failure handling request", e);
             response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
-            return;
         }
     }
 
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ClusterApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ClusterApiServlet.java
index a3ad089..ce30637 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ClusterApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ClusterApiServlet.java
@@ -66,7 +66,7 @@
 
     @Override
     protected void get(IServletRequest request, IServletResponse response) throws IOException {
-        HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, HttpUtil.Encoding.UTF8);
+        HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, request);
         PrintWriter responseWriter = response.writer();
         try {
             ObjectNode json;
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ClusterControllerDetailsApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ClusterControllerDetailsApiServlet.java
index 82ade81..6a91fa3 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ClusterControllerDetailsApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ClusterControllerDetailsApiServlet.java
@@ -48,6 +48,7 @@
 
     @Override
     protected void get(IServletRequest request, IServletResponse response) throws IOException {
+        HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, request);
         PrintWriter responseWriter = response.writer();
         IHyracksClientConnection hcc = (IHyracksClientConnection) ctx.get(HYRACKS_CONNECTION_ATTR);
         try {
@@ -58,7 +59,6 @@
             } else {
                 json = processNode(request, hcc);
             }
-            HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, HttpUtil.Encoding.UTF8);
             responseWriter.write(OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(json));
         } catch (IllegalArgumentException e) { // NOSONAR - exception not logged or rethrown
             response.setStatus(HttpResponseStatus.NOT_FOUND);
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ConnectorApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ConnectorApiServlet.java
index 5fe88f0..e422c24 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ConnectorApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ConnectorApiServlet.java
@@ -69,7 +69,7 @@
     protected void get(IServletRequest request, IServletResponse response) {
         response.setStatus(HttpResponseStatus.OK);
         try {
-            HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, HttpUtil.Encoding.UTF8);
+            HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, request);
         } catch (IOException e) {
             LOGGER.log(Level.WARN, "Failure setting content type", e);
             response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/DiagnosticsApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/DiagnosticsApiServlet.java
index c4bd054..e294510 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/DiagnosticsApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/DiagnosticsApiServlet.java
@@ -63,7 +63,7 @@
 
     @Override
     protected void get(IServletRequest request, IServletResponse response) throws IOException {
-        HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, HttpUtil.Encoding.UTF8);
+        HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, request);
         PrintWriter responseWriter = response.writer();
         response.setStatus(HttpResponseStatus.OK);
         try {
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NetDiagnosticsApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NetDiagnosticsApiServlet.java
index badb568..ffcb74d 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NetDiagnosticsApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NetDiagnosticsApiServlet.java
@@ -47,7 +47,7 @@
 
     @Override
     protected void get(IServletRequest request, IServletResponse response) throws IOException {
-        HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, HttpUtil.Encoding.UTF8);
+        HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, request);
         response.setStatus(HttpResponseStatus.OK);
         final JsonNode netDiagnostics = getNetDiagnostics();
         final PrintWriter responseWriter = response.writer();
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NodeControllerDetailsApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NodeControllerDetailsApiServlet.java
index 09a07a1..a5ae552 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NodeControllerDetailsApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/NodeControllerDetailsApiServlet.java
@@ -52,6 +52,7 @@
 
     @Override
     protected void get(IServletRequest request, IServletResponse response) throws IOException {
+        HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, request);
         PrintWriter responseWriter = response.writer();
         IHyracksClientConnection hcc = (IHyracksClientConnection) ctx.get(HYRACKS_CONNECTION_ATTR);
         try {
@@ -63,7 +64,6 @@
             } else {
                 json = processNode(request, hcc);
             }
-            HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, HttpUtil.Encoding.UTF8);
             responseWriter.write(OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(json));
         } catch (IllegalStateException e) { // NOSONAR - exception not logged or rethrown
             response.setStatus(HttpResponseStatus.SERVICE_UNAVAILABLE);
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryResultApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryResultApiServlet.java
index 6781f22..8fdcc41 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryResultApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryResultApiServlet.java
@@ -18,7 +18,6 @@
  */
 package org.apache.asterix.api.http.server;
 
-import java.io.PrintWriter;
 import java.util.concurrent.ConcurrentMap;
 
 import org.apache.asterix.app.result.ResultHandle;
@@ -26,10 +25,10 @@
 import org.apache.asterix.common.api.IApplicationContext;
 import org.apache.asterix.translator.IStatementExecutor.Stats;
 import org.apache.asterix.translator.SessionOutput;
-import org.apache.hyracks.api.result.ResultJobRecord;
-import org.apache.hyracks.api.result.IResultSet;
 import org.apache.hyracks.api.exceptions.ErrorCode;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.api.result.IResultSet;
+import org.apache.hyracks.api.result.ResultJobRecord;
 import org.apache.hyracks.http.api.IServletRequest;
 import org.apache.hyracks.http.api.IServletResponse;
 import org.apache.hyracks.http.server.utils.HttpUtil;
@@ -48,9 +47,7 @@
 
     @Override
     protected void get(IServletRequest request, IServletResponse response) throws Exception {
-        // TODO this seems wrong ...
-        HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_HTML, HttpUtil.Encoding.UTF8);
-        PrintWriter out = response.writer();
+        HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_HTML, request);
 
         final String strHandle = localPath(request);
         final ResultHandle handle = ResultHandle.parse(strHandle);
@@ -104,13 +101,13 @@
                 return;
             }
             response.setStatus(HttpResponseStatus.BAD_REQUEST);
-            out.println(e.getMessage());
+            response.writer().println(e.getMessage());
             LOGGER.log(Level.WARN, "Error retrieving result for \"" + strHandle + "\"", e);
         } catch (Exception e) {
             response.setStatus(HttpResponseStatus.BAD_REQUEST);
             LOGGER.log(Level.WARN, "Error retrieving result for \"" + strHandle + "\"", e);
         }
-        if (out.checkError()) {
+        if (response.writer().checkError()) {
             LOGGER.warn("Error flushing output writer for \"" + strHandle + "\"");
         }
     }
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
index a34f6b4..31b784f 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
@@ -122,7 +122,7 @@
     }
 
     @Override
-    protected void post(IServletRequest request, IServletResponse response) {
+    protected void post(IServletRequest request, IServletResponse response) throws IOException {
         handleRequest(request, response);
     }
 
@@ -509,13 +509,14 @@
         return "http://" + host + path + handlePath(delivery);
     }
 
-    private void handleRequest(IServletRequest request, IServletResponse response) {
+    private void handleRequest(IServletRequest request, IServletResponse response) throws IOException {
         final IRequestReference requestRef = receptionist.welcome(request);
         long elapsedStart = System.nanoTime();
         long errorCount = 1;
         Stats stats = new Stats();
         RequestExecutionState execution = new RequestExecutionState();
         List<ExecutionWarning> warnings = Collections.emptyList();
+        HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, request);
         PrintWriter httpWriter = response.writer();
         SessionOutput sessionOutput = createSessionOutput(httpWriter);
         QueryServiceRequestParameters param = new QueryServiceRequestParameters();
@@ -523,7 +524,6 @@
             // buffer the output until we are ready to set the status of the response message correctly
             sessionOutput.hold();
             sessionOutput.out().print("{\n");
-            HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, HttpUtil.Encoding.UTF8);
             Map<String, String> optionalParams = null;
             if (optionalParamProvider != null) {
                 optionalParams = optionalParamProvider.apply(request);
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java
index d983bfd..df09aee 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java
@@ -21,7 +21,6 @@
 import static org.apache.asterix.api.http.server.AbstractQueryApiServlet.ResultStatus.FAILED;
 
 import java.io.PrintWriter;
-import java.io.StringWriter;
 import java.util.List;
 import java.util.concurrent.ConcurrentMap;
 
@@ -66,10 +65,9 @@
         ResultStatus resultStatus = resultStatus(resultReaderStatus);
         Exception ex = extractException(resultReaderStatus);
 
-        final StringWriter stringWriter = new StringWriter();
-        final PrintWriter resultWriter = new PrintWriter(stringWriter);
+        HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, request);
+        final PrintWriter resultWriter = response.writer();
 
-        HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, HttpUtil.Encoding.UTF8);
         HttpResponseStatus httpStatus = HttpResponseStatus.OK;
 
         resultWriter.print("{\n");
@@ -84,11 +82,7 @@
         }
 
         resultWriter.print("}\n");
-        resultWriter.flush();
-        String result = stringWriter.toString();
-
         response.setStatus(httpStatus);
-        response.writer().print(result);
         if (response.writer().checkError()) {
             LOGGER.warn("Error flushing output writer");
         }
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RebalanceApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RebalanceApiServlet.java
index 51f420c..320c7aa 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RebalanceApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RebalanceApiServlet.java
@@ -87,7 +87,7 @@
     protected void delete(IServletRequest request, IServletResponse response) {
         try {
             // Sets the content type.
-            HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, HttpUtil.Encoding.UTF8);
+            HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, request);
             // Cancels all rebalance requests.
             cancelRebalance();
             // Sends the response back.
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RestApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RestApiServlet.java
index 520f85a..65fb357 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RestApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RestApiServlet.java
@@ -87,7 +87,7 @@
      * based on the Accept: header and other servlet parameters.
      */
     static SessionOutput initResponse(IServletRequest request, IServletResponse response) throws IOException {
-        HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_PLAIN, HttpUtil.Encoding.UTF8);
+        HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_PLAIN, request);
         // CLEAN_JSON output is the default; most generally useful for a
         // programmatic HTTP API
         OutputFormat format = OutputFormat.CLEAN_JSON;
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ShutdownApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ShutdownApiServlet.java
index 8c4f22d..a228f93 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ShutdownApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ShutdownApiServlet.java
@@ -71,7 +71,7 @@
         }, "Shutdown Servlet Worker");
 
         try {
-            HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, HttpUtil.Encoding.UTF8);
+            HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, request);
         } catch (IOException e) {
             LOGGER.log(Level.WARN, "Failure handling request", e);
             response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/StorageApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/StorageApiServlet.java
index 6b632a1..eaaa0f6 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/StorageApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/StorageApiServlet.java
@@ -63,7 +63,7 @@
 
     @Override
     protected void get(IServletRequest request, IServletResponse response) throws IOException {
-        HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, HttpUtil.Encoding.UTF8);
+        HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, request);
         PrintWriter responseWriter = response.writer();
         try {
             JsonNode json;
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/VersionApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/VersionApiServlet.java
index 8b615cf..80805ce 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/VersionApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/VersionApiServlet.java
@@ -53,7 +53,7 @@
         ObjectNode responseObject = OBJECT_MAPPER.createObjectNode();
         buildProperties.forEach(responseObject::put);
         try {
-            HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_PLAIN, HttpUtil.Encoding.UTF8);
+            HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_PLAIN, request);
         } catch (IOException e) {
             LOGGER.log(Level.WARN, "Failure handling request", e);
             response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
diff --git a/asterixdb/asterix-dashboard/src/main/java/org/apache/asterix/api/http/server/QueryWebInterfaceServlet.java b/asterixdb/asterix-dashboard/src/main/java/org/apache/asterix/api/http/server/QueryWebInterfaceServlet.java
index 900e72b..3ba3de9 100644
--- a/asterixdb/asterix-dashboard/src/main/java/org/apache/asterix/api/http/server/QueryWebInterfaceServlet.java
+++ b/asterixdb/asterix-dashboard/src/main/java/org/apache/asterix/api/http/server/QueryWebInterfaceServlet.java
@@ -59,7 +59,7 @@
 
     @Override
     protected void post(IServletRequest request, IServletResponse response) throws IOException {
-        HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, HttpUtil.Encoding.UTF8);
+        HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, request);
         ExternalProperties externalProperties = appCtx.getExternalProperties();
         response.setStatus(HttpResponseStatus.OK);
         ObjectNode obj = OBJECT_MAPPER.createObjectNode();
diff --git a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/util/InvokeUtil.java b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/util/InvokeUtil.java
index 9413d1b..ffd2956 100644
--- a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/util/InvokeUtil.java
+++ b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/util/InvokeUtil.java
@@ -40,6 +40,10 @@
 public class InvokeUtil {
 
     private static final Logger LOGGER = LogManager.getLogger();
+    private static final IFailedAttemptCallback defaultFailureCallback =
+            (action, attempt, isFinal, span, failure) -> LOGGER.log(Level.WARN,
+                    "failure executing action {} (attempt: {}{})", action, attempt, isFinal ? "" : ", will retry",
+                    failure);
 
     private InvokeUtil() {
     }
@@ -258,27 +262,38 @@
 
     public static <T> T retryUntilSuccessOrExhausted(Span span, ComputingAction<T> action, IRetryPolicy policy,
             IDelay delay) throws HyracksDataException {
+        return retryUntilSuccessOrExhausted(span, action, policy, delay, defaultFailureCallback);
+    }
+
+    public static <T> T retryUntilSuccessOrExhausted(Span span, ComputingAction<T> action, IRetryPolicy policy,
+            IDelay delay, IFailedAttemptCallback onFailure) throws HyracksDataException {
         Throwable failure;
         int attempt = 0;
-        do {
+        while (true) {
             attempt++;
             try {
                 return action.compute();
             } catch (Throwable th) {
                 failure = th;
-                if (!policy.retry(th)) {
-                    break;
-                }
                 try {
-                    LOGGER.log(Level.WARN, "Failure executing action {} for the {} time", action, attempt, failure);
-                    span.sleep(delay.calculate(attempt), TimeUnit.MILLISECONDS);
+                    long delayMs = delay.calculate(attempt);
+                    if (!policy.retry(th) || span.remaining(TimeUnit.MILLISECONDS) < delayMs) {
+                        onFailure.attemptFailed(action, attempt, true, span, failure);
+                        throw HyracksDataException.create(failure);
+                    } else {
+                        onFailure.attemptFailed(action, attempt, false, span, failure);
+                    }
+                    span.sleep(delayMs, TimeUnit.MILLISECONDS);
                 } catch (InterruptedException e) {
                     Thread.currentThread().interrupt();
                     throw HyracksDataException.create(e);
                 }
             }
-        } while (!span.elapsed());
-        LOGGER.log(Level.WARN, "Final Failure executing action {} after {} attempts", action, attempt, failure);
-        throw HyracksDataException.create(failure);
+        }
+    }
+
+    @FunctionalInterface
+    public interface IFailedAttemptCallback {
+        void attemptFailed(ComputingAction<?> action, int attempt, boolean isFinal, Span span, Throwable failure);
     }
 }
diff --git a/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-cc/src/main/java/org/apache/hyracks/control/cc/web/util/JSONOutputRequestHandler.java b/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-cc/src/main/java/org/apache/hyracks/control/cc/web/util/JSONOutputRequestHandler.java
index 479004d..4fd3496 100644
--- a/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-cc/src/main/java/org/apache/hyracks/control/cc/web/util/JSONOutputRequestHandler.java
+++ b/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-cc/src/main/java/org/apache/hyracks/control/cc/web/util/JSONOutputRequestHandler.java
@@ -56,7 +56,7 @@
 
         ObjectNode result = invoke(response, host, servletPath, parts);
         if (result != null) {
-            deliver(response, result);
+            deliver(request, response, result);
         }
     }
 
@@ -71,9 +71,9 @@
         return null;
     }
 
-    protected void deliver(IServletResponse response, ObjectNode result) {
+    protected void deliver(IServletRequest request, IServletResponse response, ObjectNode result) {
         try {
-            HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, HttpUtil.Encoding.UTF8);
+            HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, request);
             ObjectMapper om = new ObjectMapper();
             om.writer().writeValue(response.writer(), result);
             response.setStatus(HttpResponseStatus.OK);
diff --git a/hyracks-fullstack/hyracks/hyracks-http/pom.xml b/hyracks-fullstack/hyracks/hyracks-http/pom.xml
index 5d56c5e..b6dee00 100644
--- a/hyracks-fullstack/hyracks/hyracks-http/pom.xml
+++ b/hyracks-fullstack/hyracks/hyracks-http/pom.xml
@@ -89,5 +89,17 @@
       <groupId>org.apache.commons</groupId>
       <artifactId>commons-lang3</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.hyracks</groupId>
+      <artifactId>hyracks-util</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 </project>
\ No newline at end of file
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/ChunkedResponse.java b/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/ChunkedResponse.java
index 0aadb1e..2b2bdef 100644
--- a/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/ChunkedResponse.java
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/ChunkedResponse.java
@@ -18,9 +18,13 @@
  */
 package org.apache.hyracks.http.server;
 
+import java.io.BufferedWriter;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 
 import org.apache.hyracks.http.api.IServletResponse;
 import org.apache.hyracks.http.server.utils.HttpUtil;
@@ -64,8 +68,8 @@
     private static final Logger LOGGER = LogManager.getLogger();
     private final ChannelHandlerContext ctx;
     private final ChunkedNettyOutputStream outputStream;
-    private final PrintWriter writer;
     private final HttpServerHandler<?> handler;
+    private PrintWriter writer;
     private DefaultHttpResponse response;
     private boolean headerSent;
     private ByteBuf error;
@@ -77,7 +81,6 @@
         this.handler = handler;
         this.ctx = ctx;
         outputStream = new ChunkedNettyOutputStream(ctx, chunkSize, this);
-        writer = new PrintWriter(outputStream);
         response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR);
         response.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
         HttpUtil.setConnectionHeader(request, response);
@@ -88,7 +91,11 @@
         if (headerSent) {
             throw new IOException("Can't add more headers since the initial response was sent");
         }
-        response.headers().set(name, value);
+        String nameString = String.valueOf(name);
+        if (writer != null && nameString.equals(HttpHeaderNames.CONTENT_TYPE.toString())) {
+            throw new IOException("Can't set " + HttpHeaderNames.CONTENT_TYPE + " after writer has been accessed");
+        }
+        response.headers().set(nameString, value);
         return this;
     }
 
@@ -98,13 +105,21 @@
     }
 
     @Override
-    public PrintWriter writer() {
+    public synchronized PrintWriter writer() {
+        if (writer == null) {
+            Charset charset = io.netty.handler.codec.http.HttpUtil.getCharset(response, StandardCharsets.UTF_8);
+            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream, charset)));
+        }
         return writer;
     }
 
     @Override
     public void close() throws IOException {
-        writer.close();
+        if (writer != null) {
+            writer.close();
+        } else {
+            outputStream.close();
+        }
         if (error == null && response.status() == HttpResponseStatus.OK) {
             if (!done) {
                 respond(LastHttpContent.EMPTY_LAST_CONTENT);
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/FullResponse.java b/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/FullResponse.java
index 2b1e8b4..f1ff92c 100644
--- a/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/FullResponse.java
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/FullResponse.java
@@ -18,10 +18,14 @@
  */
 package org.apache.hyracks.http.server;
 
+import java.io.BufferedWriter;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 
 import org.apache.hyracks.http.api.IServletResponse;
 import org.apache.hyracks.http.server.utils.HttpUtil;
@@ -40,23 +44,24 @@
 public class FullResponse implements IServletResponse {
     private final ChannelHandlerContext ctx;
     private final ByteArrayOutputStream baos;
-    private final PrintWriter writer;
     private final DefaultFullHttpResponse response;
     private final HttpServerHandler<?> handler;
+    private PrintWriter writer;
     private ChannelFuture future;
 
     public FullResponse(HttpServerHandler<?> handler, ChannelHandlerContext ctx, FullHttpRequest request) {
         this.handler = handler;
         this.ctx = ctx;
         baos = new ByteArrayOutputStream();
-        writer = new PrintWriter(baos);
         response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR);
         HttpUtil.setConnectionHeader(request, response);
     }
 
     @Override
     public void close() throws IOException {
-        writer.close();
+        if (writer != null) {
+            writer.close();
+        }
         FullHttpResponse fullResponse = response.replace(Unpooled.copiedBuffer(baos.toByteArray()));
         fullResponse.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, fullResponse.content().readableBytes());
         final ChannelPromise responseCompletionPromise = ctx.newPromise();
@@ -71,7 +76,11 @@
     }
 
     @Override
-    public PrintWriter writer() {
+    public synchronized PrintWriter writer() {
+        if (writer == null) {
+            Charset charset = io.netty.handler.codec.http.HttpUtil.getCharset(response, StandardCharsets.UTF_8);
+            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(baos, charset)));
+        }
         return writer;
     }
 
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/utils/HttpUtil.java b/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/utils/HttpUtil.java
index 1fd8cd2..1a51318 100644
--- a/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/utils/HttpUtil.java
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/utils/HttpUtil.java
@@ -19,16 +19,24 @@
 package org.apache.hyracks.http.server.utils;
 
 import java.io.IOException;
+import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.OptionalDouble;
 import java.util.regex.Pattern;
+import java.util.stream.Stream;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.hyracks.http.api.IServletRequest;
 import org.apache.hyracks.http.api.IServletResponse;
 import org.apache.hyracks.http.server.BaseRequest;
 import org.apache.hyracks.http.server.FormUrlEncodedRequest;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.http.DefaultHttpResponse;
@@ -39,7 +47,9 @@
 import io.netty.util.AsciiString;
 
 public class HttpUtil {
+    private static final Logger LOGGER = LogManager.getLogger();
     private static final Pattern PARENT_DIR = Pattern.compile("/[^./]+/\\.\\./");
+    private static final String DEFAULT_RESPONSE_CHARSET = StandardCharsets.UTF_8.name();
 
     private HttpUtil() {
     }
@@ -96,7 +106,14 @@
     }
 
     public static String getRequestBody(IServletRequest request) {
-        return request.getHttpRequest().content().toString(StandardCharsets.UTF_8);
+        FullHttpRequest httpRequest = request.getHttpRequest();
+        Charset charset = io.netty.handler.codec.http.HttpUtil.getCharset(httpRequest, StandardCharsets.UTF_8);
+        return httpRequest.content().toString(charset);
+    }
+
+    public static void setContentType(IServletResponse response, String type, IServletRequest fromRequest)
+            throws IOException {
+        response.setHeader(HttpHeaderNames.CONTENT_TYPE, type + "; charset=" + getPreferredCharset(fromRequest));
     }
 
     public static void setContentType(IServletResponse response, String type, String charset) throws IOException {
@@ -109,9 +126,7 @@
 
     public static Map<String, String> getRequestHeaders(IServletRequest request) {
         Map<String, String> headers = new HashMap<>();
-        request.getHttpRequest().headers().forEach(entry -> {
-            headers.put(entry.getKey(), entry.getValue());
-        });
+        request.getHttpRequest().headers().forEach(entry -> headers.put(entry.getKey(), entry.getValue()));
         return headers;
     }
 
@@ -164,4 +179,78 @@
         final AsciiString connectionHeaderValue = keepAlive ? HttpHeaderValues.KEEP_ALIVE : HttpHeaderValues.CLOSE;
         response.headers().set(HttpHeaderNames.CONNECTION, connectionHeaderValue);
     }
+
+    public static String getPreferredCharset(IServletRequest request) {
+        return getPreferredCharset(request, DEFAULT_RESPONSE_CHARSET);
+    }
+
+    public static String getPreferredCharset(IServletRequest request, String defaultCharset) {
+        String acceptCharset = request.getHeader(HttpHeaderNames.ACCEPT_CHARSET);
+        if (acceptCharset == null) {
+            return defaultCharset;
+        }
+        // If no "q" parameter is present, the default weight is 1 [https://tools.ietf.org/html/rfc7231#section-5.3.1]
+        Optional<String> preferredCharset = Stream.of(StringUtils.split(acceptCharset, ","))
+                .map(WeightedHeaderValue::new).sorted().map(WeightedHeaderValue::getValue)
+                .map(a -> "*".equals(a) ? defaultCharset : a).filter(value -> {
+                    if (!Charset.isSupported(value)) {
+                        LOGGER.info("disregarding unsupported charset '{}'", value);
+                        return false;
+                    }
+                    return true;
+                }).findFirst();
+        return preferredCharset.orElse(defaultCharset);
+    }
+
+    private static class WeightedHeaderValue implements Comparable<WeightedHeaderValue> {
+
+        final String value;
+        final double weight;
+
+        WeightedHeaderValue(String value) {
+            // Accept-Charset = 1#( ( charset / "*" ) [ weight ] )
+            // weight = OWS ";" OWS "q=" qvalue
+            String[] splits = StringUtils.split(value, ";");
+            this.value = splits[0].trim();
+            if (splits.length == 1) {
+                weight = 1.0d;
+            } else {
+                OptionalDouble specifiedWeight = Stream.of(splits).skip(1).map(String::trim).map(String::toLowerCase)
+                        .filter(a -> a.startsWith("q="))
+                        .mapToDouble(segment -> Double.parseDouble(StringUtils.splitByWholeSeparator(segment, "q=")[0]))
+                        .findFirst();
+                this.weight = specifiedWeight.orElse(1.0d);
+            }
+        }
+
+        public String getValue() {
+            return value;
+        }
+
+        public double getWeight() {
+            return weight;
+        }
+
+        @Override
+        public int compareTo(WeightedHeaderValue o) {
+            return Double.compare(o.weight, weight);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            WeightedHeaderValue that = (WeightedHeaderValue) o;
+            return Double.compare(that.weight, weight) == 0 && Objects.equals(value, that.value);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(value, weight);
+        }
+    }
 }
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/PipelinedRequestsTest.java b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/PipelinedRequestsTest.java
index 0c96eb6..c0117f1 100644
--- a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/PipelinedRequestsTest.java
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/PipelinedRequestsTest.java
@@ -52,7 +52,7 @@
 import org.apache.hyracks.http.server.HttpServerConfigBuilder;
 import org.apache.hyracks.http.server.InterruptOnCloseHandler;
 import org.apache.hyracks.http.server.WebManager;
-import org.apache.hyracks.http.servlet.SleepyServlet;
+import org.apache.hyracks.test.http.servlet.SleepyServlet;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.junit.Assert;
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/HttpAcceptCharsetTest.java b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/HttpAcceptCharsetTest.java
new file mode 100644
index 0000000..a166e52
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/HttpAcceptCharsetTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.hyracks.test.http;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.hyracks.http.api.IServletRequest;
+import org.apache.hyracks.http.server.utils.HttpUtil;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import io.netty.handler.codec.http.HttpHeaderNames;
+
+public class HttpAcceptCharsetTest {
+    private static final Logger LOGGER = LogManager.getLogger();
+
+    @Test
+    public void testBogusAcceptCharset() {
+        IServletRequest request = withCharset("lol");
+        assertCharsetsEqual("default on bogus", StandardCharsets.UTF_8, request);
+    }
+
+    @Test
+    public void testWeightedStarDefault() {
+        IServletRequest request = withCharset("*;q=0.5,bogus;q=1.0");
+        assertCharsetsEqual("defer on unsupported", StandardCharsets.UTF_8, request);
+    }
+
+    @Test
+    public void testWeightedStar() {
+        IServletRequest request = withCharset("*;q=0.5,utf-32;q=1.0");
+        assertCharsetsEqual("defer on bogus", Charset.forName("utf-32"), request);
+    }
+
+    @Test
+    public void testUnweightedIs1_0() {
+        IServletRequest request = withCharset("utf-8;q=0.5,utf-16");
+        assertCharsetsEqual("defer on bogus", Charset.forName("utf-16"), request);
+    }
+
+    @Test
+    public void testAmbiguous() {
+        IServletRequest request = withCharset("utf-8;q=.75,utf-16;q=.75,utf-32;q=.5");
+        String preferredCharset = HttpUtil.getPreferredCharset(request);
+        Assert.assertTrue("ambiguous by weight (got: " + preferredCharset + ")",
+                preferredCharset.toLowerCase().matches("utf-(8|16)"));
+    }
+
+    private IServletRequest withCharset(String headerValue) {
+        IServletRequest request = Mockito.mock(IServletRequest.class);
+        Mockito.when(request.getHeader(HttpHeaderNames.ACCEPT_CHARSET)).thenReturn(headerValue);
+        LOGGER.info("using {} of '{}'", HttpHeaderNames.ACCEPT_CHARSET, headerValue);
+        return request;
+    }
+
+    private void assertCharsetsEqual(String message, Object charset, Object charset2) {
+        Assert.assertEquals(message, normalize(charset), normalize(charset2));
+    }
+
+    private String normalize(Object charsetObject) {
+        if (charsetObject instanceof Charset) {
+            return ((Charset) charsetObject).name().toLowerCase();
+        } else if (charsetObject instanceof String) {
+            return ((String) charsetObject).toLowerCase();
+        } else if (charsetObject instanceof IServletRequest) {
+            return HttpUtil.getPreferredCharset((IServletRequest) charsetObject).toLowerCase();
+        }
+        throw new IllegalArgumentException("unknown type: " + charsetObject.getClass());
+    }
+}
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/test/HttpRequestTask.java b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/HttpRequestTask.java
similarity index 98%
rename from hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/test/HttpRequestTask.java
rename to hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/HttpRequestTask.java
index 78226ae..516273b 100644
--- a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/test/HttpRequestTask.java
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/HttpRequestTask.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.hyracks.http.test;
+package org.apache.hyracks.test.http;
 
 import java.io.BufferedReader;
 import java.io.InputStream;
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/HttpServerEncodingTest.java b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/HttpServerEncodingTest.java
new file mode 100644
index 0000000..c71c5e4
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/HttpServerEncodingTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.hyracks.test.http;
+
+import static org.apache.hyracks.util.string.UTF8StringSample.STRING_NEEDS_2_JAVA_CHARS_1;
+import static org.apache.hyracks.util.string.UTF8StringSample.STRING_NEEDS_2_JAVA_CHARS_2;
+
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHeaders;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.apache.hyracks.http.server.HttpServer;
+import org.apache.hyracks.http.server.HttpServerConfig;
+import org.apache.hyracks.http.server.HttpServerConfigBuilder;
+import org.apache.hyracks.http.server.WebManager;
+import org.apache.hyracks.test.http.servlet.CompliantEchoServlet;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class HttpServerEncodingTest {
+    private static final Logger LOGGER = LogManager.getLogger();
+
+    private static final int PORT = 9898;
+    private static final String HOST = "localhost";
+    private static final String PROTOCOL = "http";
+    private static final String PATH = "/";
+    private static WebManager webMgr;
+    private static CloseableHttpClient client;
+    private static URI uri;
+
+    @Parameters(name = "HttpServerEncodingTest {index}: {0}")
+    public static Collection<Object[]> tests() throws Exception {
+        List<Object[]> tests = new ArrayList<>();
+        Stream.of("encoding is hard", "中文字符", "لا يوجد ترجمة لكُ", STRING_NEEDS_2_JAVA_CHARS_1,
+                STRING_NEEDS_2_JAVA_CHARS_2).forEach(input -> {
+                    Set<Charset> legalCharsets = getLegalCharsetsFor(input);
+                    legalCharsets.forEach(charsetIn -> legalCharsets.forEach(charsetOut -> tests
+                            .add(new Object[] { input + ":" + charsetIn.displayName() + "->" + charsetOut.displayName(),
+                                    input, charsetIn, charsetOut })));
+                });
+        return tests;
+    }
+
+    private static Set<Charset> getLegalCharsetsFor(String input) {
+        return Charset.availableCharsets().values().stream().filter(Charset::canEncode)
+                .filter(test -> canEncodeDecode(input, test)).collect(Collectors.toSet());
+    }
+
+    private static boolean canEncodeDecode(String input, Charset charset) {
+        if (input.equals(new String(input.getBytes(charset), charset))) {
+            return true;
+        }
+        LOGGER.info("cannot encode / decode {} with {}", input, charset.displayName());
+        return false;
+    }
+
+    @Parameter(0)
+    public String testName;
+
+    @Parameter(1)
+    public String input;
+
+    @Parameter(2)
+    public Charset inputCharset;
+
+    @Parameter(3)
+    public Charset outputCharset;
+
+    @BeforeClass
+    public static void setup() throws Exception {
+        int numExecutors = 16;
+        int serverQueueSize = 16;
+        webMgr = new WebManager();
+        final HttpServerConfig config = HttpServerConfigBuilder.custom().setThreadCount(numExecutors)
+                .setRequestQueueSize(serverQueueSize).build();
+        HttpServer server = new HttpServer(webMgr.getBosses(), webMgr.getWorkers(), PORT, config);
+        CompliantEchoServlet servlet = new CompliantEchoServlet(server.ctx(), PATH);
+        server.addServlet(servlet);
+        webMgr.add(server);
+        webMgr.start();
+        client = HttpClients.custom().build();
+        uri = new URI(PROTOCOL, null, HOST, PORT, PATH, null, null);
+
+    }
+
+    @AfterClass
+    public static void teardown() throws Exception {
+        client.close();
+        webMgr.stop();
+    }
+
+    @Test
+    public void testAcceptCharset() throws Exception {
+        RequestBuilder builder = RequestBuilder.post(uri);
+        builder.setEntity(new StringEntity(input, inputCharset));
+        builder.setCharset(inputCharset);
+        builder.setHeader(HttpHeaders.ACCEPT_CHARSET, inputCharset.name());
+        try (CloseableHttpResponse response = client.execute(builder.build())) {
+            HttpEntity entity = response.getEntity();
+            Assert.assertEquals(String.valueOf(inputCharset), ContentType.getOrDefault(entity).getCharset(),
+                    inputCharset);
+            Assert.assertEquals(String.valueOf(inputCharset), input, EntityUtils.toString(entity));
+        }
+    }
+
+}
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/test/HttpServerTest.java b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/HttpServerTest.java
similarity index 97%
rename from hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/test/HttpServerTest.java
rename to hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/HttpServerTest.java
index c1d1315..e341e2f 100644
--- a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/test/HttpServerTest.java
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/HttpServerTest.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.hyracks.http.test;
+package org.apache.hyracks.test.http;
 
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
@@ -40,15 +40,14 @@
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.util.EntityUtils;
-import org.apache.hyracks.http.HttpTestUtil;
 import org.apache.hyracks.http.server.HttpServer;
 import org.apache.hyracks.http.server.HttpServerConfig;
 import org.apache.hyracks.http.server.HttpServerConfigBuilder;
 import org.apache.hyracks.http.server.InterruptOnCloseHandler;
 import org.apache.hyracks.http.server.WebManager;
-import org.apache.hyracks.http.servlet.ChattyServlet;
-import org.apache.hyracks.http.servlet.EchoServlet;
-import org.apache.hyracks.http.servlet.SleepyServlet;
+import org.apache.hyracks.test.http.servlet.ChattyServlet;
+import org.apache.hyracks.test.http.servlet.EchoServlet;
+import org.apache.hyracks.test.http.servlet.SleepyServlet;
 import org.apache.hyracks.util.StorageUtil;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogManager;
@@ -379,12 +378,6 @@
         Assert.assertNotNull(failure);
     }
 
-    public static void setPrivateField(Object obj, String filedName, Object value) throws Exception {
-        Field f = obj.getClass().getDeclaredField(filedName);
-        f.setAccessible(true);
-        f.set(obj, value);
-    }
-
     @Test
     public void chunkedRequestTest() throws Exception {
         final WebManager webMgr = new WebManager();
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/HttpTestUtil.java b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/HttpTestUtil.java
similarity index 98%
rename from hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/HttpTestUtil.java
rename to hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/HttpTestUtil.java
index 16efc07..af5e3e3 100644
--- a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/HttpTestUtil.java
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/HttpTestUtil.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.hyracks.http;
+package org.apache.hyracks.test.http;
 
 import java.lang.management.ManagementFactory;
 import java.lang.management.MemoryPoolMXBean;
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/servlet/ChattyServlet.java b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/servlet/ChattyServlet.java
similarity index 91%
rename from hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/servlet/ChattyServlet.java
rename to hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/servlet/ChattyServlet.java
index 54bb756..c61daa9 100644
--- a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/servlet/ChattyServlet.java
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/servlet/ChattyServlet.java
@@ -16,11 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.hyracks.http.servlet;
+package org.apache.hyracks.test.http.servlet;
 
+import java.nio.charset.StandardCharsets;
 import java.util.concurrent.ConcurrentMap;
 
-import org.apache.hyracks.http.HttpTestUtil;
+import org.apache.hyracks.test.http.HttpTestUtil;
 import org.apache.hyracks.http.api.IServletRequest;
 import org.apache.hyracks.http.api.IServletResponse;
 import org.apache.hyracks.http.server.AbstractServlet;
@@ -43,7 +44,7 @@
             responseBuilder.append(line);
         }
         String responseString = responseBuilder.toString();
-        bytes = responseString.getBytes();
+        bytes = responseString.getBytes(StandardCharsets.UTF_8);
     }
 
     @Override
@@ -54,7 +55,7 @@
     @Override
     protected void get(IServletRequest request, IServletResponse response) throws Exception {
         response.setStatus(HttpResponseStatus.OK);
-        HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_HTML, HttpUtil.Encoding.UTF8);
+        HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_HTML, request);
         LOGGER.log(Level.WARN, "I am about to flood you... and a single buffer is " + bytes.length + " bytes");
         for (int i = 0; i < 100; i++) {
             response.outputStream().write(bytes);
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/servlet/EchoServlet.java b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/servlet/CompliantEchoServlet.java
similarity index 72%
copy from hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/servlet/EchoServlet.java
copy to hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/servlet/CompliantEchoServlet.java
index 9298aed..634c215 100644
--- a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/servlet/EchoServlet.java
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/servlet/CompliantEchoServlet.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.hyracks.http.servlet;
+package org.apache.hyracks.test.http.servlet;
 
 import java.util.concurrent.ConcurrentMap;
 
@@ -25,22 +25,16 @@
 import org.apache.hyracks.http.server.AbstractServlet;
 import org.apache.hyracks.http.server.utils.HttpUtil;
 
-import io.netty.handler.codec.http.HttpResponseStatus;
+public class CompliantEchoServlet extends AbstractServlet {
 
-/**
- * A servlet that echos the received request body
- */
-public class EchoServlet extends AbstractServlet {
-
-    public EchoServlet(ConcurrentMap<String, Object> ctx, String... paths) {
+    public CompliantEchoServlet(ConcurrentMap<String, Object> ctx, String... paths) {
         super(ctx, paths);
     }
 
     @Override
     protected void post(IServletRequest request, IServletResponse response) throws Exception {
-        final String requestBody = HttpUtil.getRequestBody(request);
-        response.setStatus(HttpResponseStatus.OK);
-        HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_PLAIN, HttpUtil.Encoding.UTF8);
-        response.writer().write(requestBody);
+        HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_HTML, request);
+        response.writer().write(HttpUtil.getRequestBody(request));
     }
-}
\ No newline at end of file
+
+}
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/servlet/EchoServlet.java b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/servlet/EchoServlet.java
similarity index 95%
rename from hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/servlet/EchoServlet.java
rename to hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/servlet/EchoServlet.java
index 9298aed..2da86f3 100644
--- a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/servlet/EchoServlet.java
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/servlet/EchoServlet.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.hyracks.http.servlet;
+package org.apache.hyracks.test.http.servlet;
 
 import java.util.concurrent.ConcurrentMap;
 
@@ -40,7 +40,7 @@
     protected void post(IServletRequest request, IServletResponse response) throws Exception {
         final String requestBody = HttpUtil.getRequestBody(request);
         response.setStatus(HttpResponseStatus.OK);
-        HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_PLAIN, HttpUtil.Encoding.UTF8);
+        HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_PLAIN, request);
         response.writer().write(requestBody);
     }
 }
\ No newline at end of file
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/servlet/SleepyServlet.java b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/servlet/SleepyServlet.java
similarity index 96%
rename from hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/servlet/SleepyServlet.java
rename to hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/servlet/SleepyServlet.java
index 2a5a0a9..89cac8f 100644
--- a/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/http/servlet/SleepyServlet.java
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/test/java/org/apache/hyracks/test/http/servlet/SleepyServlet.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.hyracks.http.servlet;
+package org.apache.hyracks.test.http.servlet;
 
 import java.nio.charset.StandardCharsets;
 import java.util.concurrent.ConcurrentMap;
@@ -55,7 +55,7 @@
                 }
             }
         }
-        HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_HTML, HttpUtil.Encoding.UTF8);
+        HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_HTML, request);
         response.outputStream().write("I am playing hard to get".getBytes(StandardCharsets.UTF_8));
     }
 
diff --git a/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/string/UTF8StringSample.java b/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/string/UTF8StringSample.java
index 3e6e984..6984d75 100644
--- a/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/string/UTF8StringSample.java
+++ b/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/string/UTF8StringSample.java
@@ -28,24 +28,26 @@
  * Util class to provide the sample test string
  */
 public class UTF8StringSample {
-    public static String EMPTY_STRING = "";
+    public static final String EMPTY_STRING = "";
 
-    public static char ONE_ASCII_CHAR = 'x';
-    public static char ONE_UTF8_CHAR = 'à';
+    public static final char ONE_ASCII_CHAR = 'x';
+    public static final char ONE_UTF8_CHAR = 'à';
 
-    public static String STRING_LEN_3 = "xyz";
-    public static String STRING_UTF8_3 = "锟斤拷";
-    public static String STRING_UTF8_MIX = "\uD841\uDF0E\uD841\uDF31锟X斤Y拷Zà"; // one, two, three, and four bytes
-    public static String STRING_UTF8_MIX_LOWERCASE = "\uD841\uDF0E\uD841\uDF31锟x斤y拷zà";
+    public static final String STRING_LEN_3 = "xyz";
+    public static final String STRING_UTF8_3 = "锟斤拷";
+    public static final String STRING_UTF8_MIX = "\uD841\uDF0E\uD841\uDF31锟X斤Y拷Zà"; // one, two, three, and four bytes
+    public static final String STRING_UTF8_MIX_LOWERCASE = "\uD841\uDF0E\uD841\uDF31锟x斤y拷zà";
+    public static final String STRING_NEEDS_2_JAVA_CHARS_1 = "\uD83D\uDE22\uD83D\uDE22\uD83D\uDC89\uD83D\uDC89";
+    public static final String STRING_NEEDS_2_JAVA_CHARS_2 = "😢😢💉💉";
 
-    public static String STRING_LEN_127 = generateStringRepeatBy(ONE_ASCII_CHAR, 127);
-    public static String STRING_LEN_128 = generateStringRepeatBy(ONE_ASCII_CHAR, 128);
+    public static final String STRING_LEN_127 = generateStringRepeatBy(ONE_ASCII_CHAR, 127);
+    public static final String STRING_LEN_128 = generateStringRepeatBy(ONE_ASCII_CHAR, 128);
 
-    public static String STRING_LEN_MEDIUM_SUB_1 = generateStringRepeatBy(ONE_ASCII_CHAR, BOUND_TWO_BYTE - 1);
-    public static String STRING_LEN_MEDIUM = generateStringRepeatBy(ONE_ASCII_CHAR, BOUND_TWO_BYTE);
+    public static final String STRING_LEN_MEDIUM_SUB_1 = generateStringRepeatBy(ONE_ASCII_CHAR, BOUND_TWO_BYTE - 1);
+    public static final String STRING_LEN_MEDIUM = generateStringRepeatBy(ONE_ASCII_CHAR, BOUND_TWO_BYTE);
 
-    public static String STRING_LEN_LARGE_SUB_1 = generateStringRepeatBy(ONE_ASCII_CHAR, BOUND_THREE_BYTE - 1);
-    public static String STRING_LEN_LARGE = generateStringRepeatBy(ONE_ASCII_CHAR, BOUND_THREE_BYTE);
+    public static final String STRING_LEN_LARGE_SUB_1 = generateStringRepeatBy(ONE_ASCII_CHAR, BOUND_THREE_BYTE - 1);
+    public static final String STRING_LEN_LARGE = generateStringRepeatBy(ONE_ASCII_CHAR, BOUND_THREE_BYTE);
 
     public static String generateStringRepeatBy(char c, int times) {
         char[] chars = new char[times];
@@ -53,4 +55,6 @@
         return new String(chars);
     }
 
+    private UTF8StringSample() {
+    }
 }
diff --git a/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/string/UTF8StringUtilTest.java b/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/string/UTF8StringUtilTest.java
index 5a614f0..c48e1d8 100644
--- a/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/string/UTF8StringUtilTest.java
+++ b/hyracks-fullstack/hyracks/hyracks-util/src/test/java/org/apache/hyracks/util/string/UTF8StringUtilTest.java
@@ -38,13 +38,11 @@
 import static org.apache.hyracks.util.string.UTF8StringUtil.normalize;
 import static org.apache.hyracks.util.string.UTF8StringUtil.rawByteCompareTo;
 import static org.apache.hyracks.util.string.UTF8StringUtil.hash;
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
 
-import org.junit.Assert;
 import org.junit.Test;
 
 public class UTF8StringUtilTest {