cxf-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dk...@apache.org
Subject cxf git commit: Rework the throttling mechanism to also allow it to work for common rate limitting cases.
Date Wed, 25 Mar 2015 17:28:05 GMT
Repository: cxf
Updated Branches:
  refs/heads/master da4ccb008 -> 9ac66adff


Rework the throttling mechanism to also allow it to work for common rate limitting cases.


Project: http://git-wip-us.apache.org/repos/asf/cxf/repo
Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/9ac66adf
Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/9ac66adf
Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/9ac66adf

Branch: refs/heads/master
Commit: 9ac66adffb73f3474fde064fab1013ecdd24be7c
Parents: da4ccb0
Author: Daniel Kulp <dkulp@apache.org>
Authored: Wed Mar 25 13:26:42 2015 -0400
Committer: Daniel Kulp <dkulp@apache.org>
Committed: Wed Mar 25 13:27:57 2015 -0400

----------------------------------------------------------------------
 .../java/org/apache/cxf/message/Message.java    |  1 +
 .../java/demo/throttling/client/Client.java     | 23 ++++--
 .../java/demo/throttling/server/Customer.java   | 39 ++++++----
 .../java/demo/throttling/server/Server.java     | 11 ++-
 .../apache/cxf/throttling/ThrottleResponse.java | 75 ++++++++++++++++++++
 .../cxf/throttling/ThrottlingFeature.java       |  2 +
 .../cxf/throttling/ThrottlingInterceptor.java   |  7 +-
 .../cxf/throttling/ThrottlingManager.java       |  4 +-
 .../ThrottlingResponseInterceptor.java          | 67 +++++++++++++++++
 .../http_jetty/JettyHTTPServerEngine.java       |  7 +-
 .../transport/http/AbstractHTTPDestination.java |  7 ++
 11 files changed, 217 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cxf/blob/9ac66adf/core/src/main/java/org/apache/cxf/message/Message.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/cxf/message/Message.java b/core/src/main/java/org/apache/cxf/message/Message.java
index 0695b50..b748eb8 100644
--- a/core/src/main/java/org/apache/cxf/message/Message.java
+++ b/core/src/main/java/org/apache/cxf/message/Message.java
@@ -101,6 +101,7 @@ public interface Message extends StringMap {
     
     String PROTOCOL_HEADERS = Message.class.getName() + ".PROTOCOL_HEADERS";
     String RESPONSE_CODE = Message.class.getName() + ".RESPONSE_CODE";
+    String ERROR_MESSAGE = Message.class.getName() + ".ERROR_MESSAGE";
     String ENDPOINT_ADDRESS = Message.class.getName() + ".ENDPOINT_ADDRESS";
     String PATH_INFO = Message.class.getName() + ".PATH_INFO";
     String QUERY_STRING = Message.class.getName() + ".QUERY_STRING";

http://git-wip-us.apache.org/repos/asf/cxf/blob/9ac66adf/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/client/Client.java
----------------------------------------------------------------------
diff --git a/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/client/Client.java
b/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/client/Client.java
index 7e191e7..80e347d 100644
--- a/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/client/Client.java
+++ b/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/client/Client.java
@@ -46,10 +46,11 @@ public final class Client implements Runnable {
     @Override
     public void run() {
         long start = System.currentTimeMillis();
+        int x = 0;
+        boolean exceeded = false;
         try (Greeter port = service.getSoapPort()) {
             port.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, username);
             port.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "password");
-            int x = 0;
             do {
                 if (doStop) {
                     break;
@@ -57,12 +58,20 @@ public final class Client implements Runnable {
                 port.greetMe(username + "-" + x);
                 x++;
             } while (x < 10000);
-            long end = System.currentTimeMillis();
-            double rate = x * 1000 / (end - start);
-            System.out.println(username + " finished " + x + " invocations: " + rate + "
req/sec");
+        } catch (javax.xml.ws.WebServiceException wse) {
+            if (wse.getCause().getMessage().contains("429")){
+                //exceeded are allowable number of requests
+                exceeded = true;
+            } else {
+                wse.printStackTrace();
+            }
         } catch (Exception ex) {
             ex.printStackTrace();
         }
+        long end = System.currentTimeMillis();
+        double rate = x * 1000 / (end - start);
+        System.out.println(username + " finished " + x + " invocations: " + rate + " req/sec
" 
+            + (exceeded ? "(exceeded max)" : ""));
     }
     public void stop() {
         doStop = true;
@@ -85,7 +94,8 @@ public final class Client implements Runnable {
         System.out.println(wsdlURL);
         SOAPService ss = new SOAPService(wsdlURL, SERVICE_NAME);
         List<Client> c = new ArrayList<Client>();
-        Client client = new Client("Tom", ss);
+        Client client;
+        client = new Client("Tom", ss);
         new Thread(client).start();
         c.add(client);
         client = new Client("Rob", ss);
@@ -97,6 +107,9 @@ public final class Client implements Runnable {
         client = new Client("Malcolm", ss);
         new Thread(client).start();
         c.add(client);
+        client = new Client("Jonas", ss);
+        new Thread(client).start();
+        c.add(client);
         
         System.out.println("Sleeping on main thread for 60 seconds");
         Thread.sleep(60000);

http://git-wip-us.apache.org/repos/asf/cxf/blob/9ac66adf/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/server/Customer.java
----------------------------------------------------------------------
diff --git a/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/server/Customer.java
b/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/server/Customer.java
index eecad80..012463f 100644
--- a/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/server/Customer.java
+++ b/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/server/Customer.java
@@ -21,9 +21,9 @@ package demo.throttling.server;
 
 import com.codahale.metrics.MetricRegistry;
 
-import org.apache.cxf.message.Message;
 import org.apache.cxf.metrics.MetricsContext;
 import org.apache.cxf.metrics.codahale.CodahaleMetricsContext;
+import org.apache.cxf.throttling.ThrottleResponse;
 
 /**
  * 
@@ -43,55 +43,52 @@ public abstract class Customer {
         return metrics;
     }
 
-    public abstract long throttle(Message m);
+    public abstract void throttle(ThrottleResponse r);
     
     
     public static class PremiumCustomer extends Customer {
         public PremiumCustomer(String n) {
             super(n);
         }
-        public long throttle(Message m) {
+        public void throttle(ThrottleResponse m) {
             //Premium customers are unthrottled
-            return 0;
         }
     }
     public static class PreferredCustomer extends Customer {
         public PreferredCustomer(String n) {
             super(n);
         }
-        public long throttle(Message m) {
+        public void throttle(ThrottleResponse m) {
             //System.out.println("p  " + metrics.getTotals().getOneMinuteRate() + "  " +
metrics.getTotals().getCount());
             //Preferred customers are unthrottled until they hit 100req/sec, then start delaying
by .05 seconds
             //(drops to max of 50req/sec until below the 100req/sec rate)
             if (metrics.getTotals().getOneMinuteRate() > 100) {
-                return 20;
+                m.setDelay(20);
             }
-            return 0;
         }
     }
     public static class RegularCustomer extends Customer {
         public RegularCustomer(String n) {
             super(n);
         }
-        public long throttle(Message m) {
+        public void throttle(ThrottleResponse m) {
             //Regular customers are unthrottled until they hit 25req/sec, then start delaying
by 0.25 seconds 
             //(drops to max of 4req/sec until below the 25req/sec rate)
             if (metrics.getTotals().getOneMinuteRate() > 25) {
-                return 250;
+                m.setDelay(250);
             }
             //They also get throttled more if they are over 10req/sec over a 5 minute period
 
             //(drops to max of 2req/sec until below the 10req/sec rate)
             if (metrics.getTotals().getFiveMinuteRate() > 10) {
-                return 500;
+                m.setDelay(500);
             }
-            return 0;
         }
     }
     public static class CheapCustomer extends Customer {
         public CheapCustomer(String n) {
             super(n);
         }
-        public long throttle(Message m) {
+        public void throttle(ThrottleResponse m) {
             //System.out.println("ch  " + metrics.getTotals().getOneMinuteRate() + "  " +
metrics.getTotals().getCount());
             //Cheap customers are always get a .1 sec delay
             long delay = 100;
@@ -103,7 +100,23 @@ public abstract class Customer {
             if (metrics.getTotals().getFiveMinuteRate() > 1) {
                 delay += 1000;
             }
-            return delay;
+            m.setDelay(delay);
+        }
+    }
+
+    public static class TrialCustomer extends Customer {
+        long lastTime = System.currentTimeMillis();
+        public TrialCustomer(String n) {
+            super(n);
+        }
+        public void throttle(ThrottleResponse m) {
+            //Trial customers only get 10 requests, then rejected
+            if (metrics.getTotals().getCount() >= 10) {
+                m.setResponseCode(429, "Exceeded");
+            }
+            m.addResponseHeader("X-RateLimit-Limit", "10");
+            m.addResponseHeader("X-RateLimit-Remaining", Long.toString(10 - metrics.getTotals().getCount()));
+            m.setDelay(100);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/cxf/blob/9ac66adf/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/server/Server.java
----------------------------------------------------------------------
diff --git a/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/server/Server.java
b/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/server/Server.java
index 28600fa..05b726c 100644
--- a/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/server/Server.java
+++ b/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/server/Server.java
@@ -33,6 +33,7 @@ import org.apache.cxf.BusFactory;
 import org.apache.cxf.message.Message;
 import org.apache.cxf.metrics.MetricsFeature;
 import org.apache.cxf.phase.Phase;
+import org.apache.cxf.throttling.ThrottleResponse;
 import org.apache.cxf.throttling.ThrottlingFeature;
 import org.apache.cxf.throttling.ThrottlingManager;
 
@@ -46,6 +47,7 @@ public class Server {
         customers.put("Rob", new Customer.PreferredCustomer("Rob"));
         customers.put("Vince", new Customer.RegularCustomer("Vince"));
         customers.put("Malcolm", new Customer.CheapCustomer("Malcolm"));
+        customers.put("Jonas", new Customer.TrialCustomer("Jonas"));
         
         Bus b = BusFactory.getDefaultBus();
         MetricRegistry registry = new MetricRegistry();
@@ -53,19 +55,22 @@ public class Server {
         
         ThrottlingManager manager = new ThrottlingManager() {
             @Override
-            public long getThrottleDelay(String phase, Message m) {
+            public ThrottleResponse getThrottleResponse(String phase, Message m) {
+                ThrottleResponse r = new ThrottleResponse();
                 if (m.get("THROTTLED") != null) {
-                    return 0;
+                    return null;
                 }
                 m.put("THROTTLED", true);
                 Customer c = m.getExchange().get(Customer.class);
-                return c.throttle(m);
+                c.throttle(r);
+                return r;
             }
 
             @Override
             public List<String> getDecisionPhases() {
                 return Collections.singletonList(Phase.PRE_STREAM);
             }
+
         };
         b.getInInterceptors().add(new CustomerMetricsInterceptor(registry, customers));
         

http://git-wip-us.apache.org/repos/asf/cxf/blob/9ac66adf/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottleResponse.java
----------------------------------------------------------------------
diff --git a/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottleResponse.java
b/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottleResponse.java
new file mode 100644
index 0000000..34ba5d5
--- /dev/null
+++ b/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottleResponse.java
@@ -0,0 +1,75 @@
+/**
+ * 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.cxf.throttling;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 
+ */
+public class ThrottleResponse {
+    protected long delay;
+    protected Map<String, String> responseHeaders = new HashMap<>();
+    protected int responseCode = -1;
+    protected String errorMessage;
+    
+
+    public Map<String, String> getResponseHeaders() {
+        return responseHeaders;
+    }
+    /**
+     * Add headers to the response.  Typically, this would be things like X-RateLimit-Limit
headers
+     * @return
+     */
+    public ThrottleResponse addResponseHeader(String header, String value) {
+        responseHeaders.put(header, value);
+        return this;
+    }
+
+    public int getResponseCode() {
+        return responseCode;
+    }
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    public ThrottleResponse setResponseCode(int rc) {
+        return setResponseCode(rc, null);
+    }
+    public ThrottleResponse setResponseCode(int rc, String msg) {
+        this.responseCode = rc;
+        this.errorMessage = msg;
+        return this;
+    }
+
+    /**
+     * Delay processing for specified milliseconds.  Should be "small" to prevent the client
from timing out.
+     * @return
+     */
+    public long getDelay() {
+        return delay;
+    }
+
+    public ThrottleResponse setDelay(long d) {
+        this.delay = d;
+        return this;
+    }    
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/9ac66adf/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingFeature.java
----------------------------------------------------------------------
diff --git a/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingFeature.java
b/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingFeature.java
index 3580213..4276564 100644
--- a/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingFeature.java
+++ b/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingFeature.java
@@ -49,5 +49,7 @@ public class ThrottlingFeature extends AbstractFeature {
         for (String p : m.getDecisionPhases()) {
             provider.getInInterceptors().add(new ThrottlingInterceptor(p, m));
         }
+        provider.getOutInterceptors().add(new ThrottlingResponseInterceptor());
+        provider.getOutFaultInterceptors().add(new ThrottlingResponseInterceptor());
     }
 }

http://git-wip-us.apache.org/repos/asf/cxf/blob/9ac66adf/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingInterceptor.java
----------------------------------------------------------------------
diff --git a/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingInterceptor.java
b/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingInterceptor.java
index d56e286..e0f598c 100644
--- a/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingInterceptor.java
+++ b/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingInterceptor.java
@@ -42,7 +42,12 @@ public class ThrottlingInterceptor extends AbstractPhaseInterceptor<Message>
{
 
     @Override
     public void handleMessage(Message message) throws Fault {
-        long l = manager.getThrottleDelay(getPhase(), message);
+        ThrottleResponse rsp = manager.getThrottleResponse(getPhase(), message);
+        if (rsp == null) {
+            return;
+        }
+        message.getExchange().put(ThrottleResponse.class, rsp);
+        long l = rsp.getDelay();
         if (l > 0) {
             ContinuationProvider cp = message.get(ContinuationProvider.class);
             if (cp == null) {

http://git-wip-us.apache.org/repos/asf/cxf/blob/9ac66adf/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingManager.java
----------------------------------------------------------------------
diff --git a/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingManager.java
b/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingManager.java
index 59d7289..86094a9 100644
--- a/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingManager.java
+++ b/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingManager.java
@@ -37,10 +37,10 @@ public interface ThrottlingManager {
     List<String> getDecisionPhases();
     
     /**
-     * Returns the number of milliseconds the request should be delayed before further processing
+     * Use information in the message to determine what throttling measures should be taken
      * @param phase
      * @param m
      * @return
      */
-    long getThrottleDelay(String phase, Message m);
+    ThrottleResponse getThrottleResponse(String phase, Message m);
 }

http://git-wip-us.apache.org/repos/asf/cxf/blob/9ac66adf/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingResponseInterceptor.java
----------------------------------------------------------------------
diff --git a/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingResponseInterceptor.java
b/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingResponseInterceptor.java
new file mode 100644
index 0000000..5604c9a
--- /dev/null
+++ b/rt/features/throttling/src/main/java/org/apache/cxf/throttling/ThrottlingResponseInterceptor.java
@@ -0,0 +1,67 @@
+/**
+ * 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.cxf.throttling;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.AbstractPhaseInterceptor;
+import org.apache.cxf.phase.Phase;
+
+/**
+ * 
+ */
+public class ThrottlingResponseInterceptor extends AbstractPhaseInterceptor<Message>
{
+    public ThrottlingResponseInterceptor() {
+        super(Phase.SETUP);
+    }
+
+    @Override
+    public void handleMessage(Message message) throws Fault {
+        ThrottleResponse rsp = message.getExchange().get(ThrottleResponse.class);
+        if (rsp != null) {
+            if (rsp.getResponseCode() > 0) {
+                message.put(Message.RESPONSE_CODE, rsp.getResponseCode());
+                if (rsp.getErrorMessage() != null) {
+                    message.put(Message.ERROR_MESSAGE, rsp.getErrorMessage());
+                }
+            }
+            Map<String, List<String>> headers = CastUtils.cast((Map<?, ?>)message.get(Message.PROTOCOL_HEADERS));
+            
+            if (headers == null) {
+                headers = new TreeMap<String, List<String>>(String.CASE_INSENSITIVE_ORDER);
+                message.put(Message.PROTOCOL_HEADERS, headers);
+            }
+            for (Map.Entry<String, String> e : rsp.getResponseHeaders().entrySet())
{
+                List<String> r = headers.get(e.getKey());
+                if (r == null) {
+                    r = new ArrayList<String>();
+                    headers.put(e.getKey(), r);
+                }
+                r.add(e.getValue());
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/9ac66adf/rt/transports/http-jetty/src/main/java/org/apache/cxf/transport/http_jetty/JettyHTTPServerEngine.java
----------------------------------------------------------------------
diff --git a/rt/transports/http-jetty/src/main/java/org/apache/cxf/transport/http_jetty/JettyHTTPServerEngine.java
b/rt/transports/http-jetty/src/main/java/org/apache/cxf/transport/http_jetty/JettyHTTPServerEngine.java
index 34458a7..acc2ecc 100644
--- a/rt/transports/http-jetty/src/main/java/org/apache/cxf/transport/http_jetty/JettyHTTPServerEngine.java
+++ b/rt/transports/http-jetty/src/main/java/org/apache/cxf/transport/http_jetty/JettyHTTPServerEngine.java
@@ -353,8 +353,11 @@ public class JettyHTTPServerEngine implements ServerEngine {
                 public void handle(String target, Request baseRequest, 
                                    HttpServletRequest request, HttpServletResponse response)

                     throws IOException {
-                    String msg = HttpStatus.getMessage(response.getStatus());
-                    request.setAttribute(RequestDispatcher.ERROR_MESSAGE, msg);
+                    String msg = (String)request.getAttribute(RequestDispatcher.ERROR_MESSAGE);
+                    if (StringUtils.isEmpty(msg) || msg.contains("org.apache.cxf.interceptor.Fault"))
{
+                        msg = HttpStatus.getMessage(response.getStatus());
+                        request.setAttribute(RequestDispatcher.ERROR_MESSAGE, msg);
+                    }
                     if (response instanceof Response) {
                         //need to use the deprecated method to support compiling with Jetty
8
                         ((Response)response).setStatus(response.getStatus(), msg);

http://git-wip-us.apache.org/repos/asf/cxf/blob/9ac66adf/rt/transports/http/src/main/java/org/apache/cxf/transport/http/AbstractHTTPDestination.java
----------------------------------------------------------------------
diff --git a/rt/transports/http/src/main/java/org/apache/cxf/transport/http/AbstractHTTPDestination.java
b/rt/transports/http/src/main/java/org/apache/cxf/transport/http/AbstractHTTPDestination.java
index 6b1714f..ee4ea40 100644
--- a/rt/transports/http/src/main/java/org/apache/cxf/transport/http/AbstractHTTPDestination.java
+++ b/rt/transports/http/src/main/java/org/apache/cxf/transport/http/AbstractHTTPDestination.java
@@ -613,6 +613,13 @@ public abstract class AbstractHTTPDestination
         HttpServletResponse response = getHttpResponseFromMessage(outMessage);
 
         int responseCode = getReponseCodeFromMessage(outMessage);
+        if (responseCode >= 300) {
+            String ec = (String)outMessage.get(Message.ERROR_MESSAGE);
+            if (!StringUtils.isEmpty(ec)) {
+                response.sendError(responseCode, ec);
+                return null;
+            }
+        }
         response.setStatus(responseCode);
         new Headers(outMessage).copyToResponse(response);
 


Mime
View raw message