1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 package org.apache.axis2.transport.http;
21
22 import org.apache.axiom.om.OMOutputFormat;
23 import org.apache.axis2.AxisFault;
24 import org.apache.axis2.Constants;
25 import org.apache.axis2.addressing.EndpointReference;
26 import org.apache.axis2.context.ConfigurationContext;
27 import org.apache.axis2.context.MessageContext;
28 import org.apache.axis2.description.Parameter;
29 import org.apache.axis2.description.TransportOutDescription;
30 import org.apache.axis2.handlers.AbstractHandler;
31 import org.apache.axis2.transport.MessageFormatter;
32 import org.apache.axis2.transport.OutTransportInfo;
33 import org.apache.axis2.transport.TransportSender;
34 import org.apache.axis2.transport.TransportUtils;
35 import org.apache.axis2.transport.http.server.AxisHttpResponseImpl;
36 import org.apache.axis2.util.JavaUtils;
37 import org.apache.commons.httpclient.Header;
38 import org.apache.commons.httpclient.HttpException;
39 import org.apache.commons.httpclient.HttpMethod;
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42
43 import javax.xml.stream.FactoryConfigurationError;
44 import javax.servlet.http.HttpServletResponse;
45 import java.io.IOException;
46 import java.io.OutputStream;
47 import java.net.MalformedURLException;
48 import java.net.URL;
49 import java.util.Iterator;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.zip.GZIPOutputStream;
53
54 public class CommonsHTTPTransportSender extends AbstractHandler implements
55 TransportSender {
56
57 protected static final String PROXY_HOST_NAME = "proxy_host";
58
59 protected static final String PROXY_PORT = "proxy_port";
60
61 int soTimeout = HTTPConstants.DEFAULT_SO_TIMEOUT;
62
63 /**
64 * proxydiscription
65 */
66 protected TransportOutDescription proxyOutSetting = null;
67
68 private static final Log log = LogFactory
69 .getLog(CommonsHTTPTransportSender.class);
70
71 protected String httpVersion = HTTPConstants.HEADER_PROTOCOL_11;
72
73 private boolean chunked = false;
74
75 int connectionTimeout = HTTPConstants.DEFAULT_CONNECTION_TIMEOUT;
76
77 public void cleanup(MessageContext msgContext) throws AxisFault {
78 HttpMethod httpMethod = (HttpMethod) msgContext
79 .getProperty(HTTPConstants.HTTP_METHOD);
80
81 if (httpMethod != null) {
82 httpMethod.releaseConnection();
83 }
84 }
85
86 public void init(ConfigurationContext confContext,
87 TransportOutDescription transportOut) throws AxisFault {
88
89 // <parameter name="PROTOCOL">HTTP/1.0</parameter> or
90 // <parameter name="PROTOCOL">HTTP/1.1</parameter> is
91 // checked
92 Parameter version = transportOut
93 .getParameter(HTTPConstants.PROTOCOL_VERSION);
94 if (version != null) {
95 if (HTTPConstants.HEADER_PROTOCOL_11.equals(version.getValue())) {
96 httpVersion = HTTPConstants.HEADER_PROTOCOL_11;
97
98 Parameter transferEncoding = transportOut
99 .getParameter(HTTPConstants.HEADER_TRANSFER_ENCODING);
100
101 if ((transferEncoding != null)
102 && HTTPConstants.HEADER_TRANSFER_ENCODING_CHUNKED
103 .equals(transferEncoding.getValue())) {
104 chunked = true;
105 }
106 } else if (HTTPConstants.HEADER_PROTOCOL_10.equals(version
107 .getValue())) {
108 httpVersion = HTTPConstants.HEADER_PROTOCOL_10;
109 } else {
110 throw new AxisFault("Parameter "
111 + HTTPConstants.PROTOCOL_VERSION
112 + " Can have values only HTTP/1.0 or HTTP/1.1");
113 }
114 }
115
116 // Get the timeout values from the configuration
117 try {
118 Parameter tempSoTimeoutParam = transportOut
119 .getParameter(HTTPConstants.SO_TIMEOUT);
120 Parameter tempConnTimeoutParam = transportOut
121 .getParameter(HTTPConstants.CONNECTION_TIMEOUT);
122
123 if (tempSoTimeoutParam != null) {
124 soTimeout = Integer.parseInt((String) tempSoTimeoutParam
125 .getValue());
126 }
127
128 if (tempConnTimeoutParam != null) {
129 connectionTimeout = Integer
130 .parseInt((String) tempConnTimeoutParam.getValue());
131 }
132 } catch (NumberFormatException nfe) {
133
134 // If there's a problem log it and use the default values
135 log.error("Invalid timeout value format: not a number", nfe);
136 }
137 }
138
139 public void stop() {
140 // Any code that , need to invoke when sender stop
141 }
142
143 public InvocationResponse invoke(MessageContext msgContext)
144 throws AxisFault {
145 try {
146 OMOutputFormat format = new OMOutputFormat();
147 // if (!msgContext.isDoingMTOM())
148 msgContext.setDoingMTOM(TransportUtils.doWriteMTOM(msgContext));
149 msgContext.setDoingSwA(TransportUtils.doWriteSwA(msgContext));
150 msgContext.setDoingREST(TransportUtils.isDoingREST(msgContext));
151 format.setSOAP11(msgContext.isSOAP11());
152 format.setDoOptimize(msgContext.isDoingMTOM());
153 format.setDoingSWA(msgContext.isDoingSwA());
154 format.setCharSetEncoding(TransportUtils.getCharSetEncoding(msgContext));
155
156 Object mimeBoundaryProperty = msgContext
157 .getProperty(Constants.Configuration.MIME_BOUNDARY);
158 if (mimeBoundaryProperty != null) {
159 format.setMimeBoundary((String) mimeBoundaryProperty);
160 }
161
162 TransportOutDescription transportOut = msgContext.getConfigurationContext().
163 getAxisConfiguration().getTransportOut(Constants.TRANSPORT_HTTP);
164
165 // set the timeout properteies
166
167 Parameter soTimeoutParam = transportOut.getParameter(HTTPConstants.SO_TIMEOUT);
168 Parameter connTimeoutParam = transportOut.getParameter(HTTPConstants.CONNECTION_TIMEOUT);
169
170 // set the property valuse only if they are not set by the user explicitly
171 if ((soTimeoutParam != null) && (msgContext.getProperty(HTTPConstants.SO_TIMEOUT) == null)) {
172 msgContext.setProperty(HTTPConstants.SO_TIMEOUT, new Integer((String)soTimeoutParam.getValue()));
173 }
174
175 if ((connTimeoutParam != null) && (msgContext.getProperty(HTTPConstants.CONNECTION_TIMEOUT) == null)) {
176 msgContext.setProperty(HTTPConstants.CONNECTION_TIMEOUT, new Integer((String)connTimeoutParam.getValue()));
177 }
178
179 //if a parameter has set been set, we will omit the SOAP action for SOAP 1.2
180 if (transportOut != null) {
181 if (!msgContext.isSOAP11()) {
182 Parameter param = transportOut.getParameter(HTTPConstants.OMIT_SOAP_12_ACTION);
183 Object parameterValue = null;
184 if (param != null) {
185 parameterValue = param.getValue();
186 }
187
188 if (parameterValue != null && JavaUtils.isTrueExplicitly(parameterValue)) {
189 //Check whether user has already overridden this.
190 Object propertyValue = msgContext.getProperty(Constants.Configuration.DISABLE_SOAP_ACTION);
191 if (propertyValue == null || !JavaUtils.isFalseExplicitly(propertyValue)) {
192 msgContext.setProperty(Constants.Configuration.DISABLE_SOAP_ACTION,
193 Boolean.TRUE);
194 }
195 }
196 }
197 }
198
199 // Transport URL can be different from the WSA-To. So processing
200 // that now.
201 EndpointReference epr = null;
202 String transportURL = (String) msgContext
203 .getProperty(Constants.Configuration.TRANSPORT_URL);
204
205 if (transportURL != null) {
206 epr = new EndpointReference(transportURL);
207 } else if (msgContext.getTo() != null
208 && !msgContext.getTo().hasAnonymousAddress()) {
209 epr = msgContext.getTo();
210 }
211
212 // Check for the REST behavior, if you desire rest behavior
213 // put a <parameter name="doREST" value="true"/> at the
214 // server.xml/client.xml file
215 // ######################################################
216 // Change this place to change the wsa:toepr
217 // epr = something
218 // ######################################################
219
220 if (epr != null) {
221 if (!epr.hasNoneAddress()) {
222 writeMessageWithCommons(msgContext, epr, format);
223 }else{
224 if(msgContext.isFault()){
225 if(log.isDebugEnabled()){
226 log.debug("Fault sent to WS-A None URI: "+msgContext.getEnvelope().getBody().getFault());
227 }
228 }
229 }
230 } else {
231 if (msgContext.getProperty(MessageContext.TRANSPORT_OUT) != null) {
232 sendUsingOutputStream(msgContext, format);
233 TransportUtils.setResponseWritten(msgContext, true);
234 } else {
235 throw new AxisFault("Both the TO and MessageContext.TRANSPORT_OUT property " +
236 "are null, so nowhere to send");
237 }
238 }
239 } catch (FactoryConfigurationError e) {
240 log.debug(e);
241 throw AxisFault.makeFault(e);
242 } catch (IOException e) {
243 log.debug(e);
244 throw AxisFault.makeFault(e);
245 }
246 return InvocationResponse.CONTINUE;
247 }
248
249 /**
250 * Send a message (which must be a response) via the OutputStream sitting in the
251 * MessageContext TRANSPORT_OUT property. Since this class is used for both requests and
252 * responses, we split the logic - this method always gets called when we're
253 * writing to the HTTP response stream, and sendUsingCommons() is used for requests.
254 *
255 * @param msgContext the active MessageContext
256 * @param format output formatter for our message
257 * @throws AxisFault if a general problem arises
258 */
259 private void sendUsingOutputStream(MessageContext msgContext,
260 OMOutputFormat format) throws AxisFault {
261 OutputStream out = (OutputStream) msgContext.getProperty(MessageContext.TRANSPORT_OUT);
262
263 // I Don't think we need this check.. Content type needs to be set in
264 // any case. (thilina)
265 // if (msgContext.isServerSide()) {
266 OutTransportInfo transportInfo = (OutTransportInfo) msgContext
267 .getProperty(Constants.OUT_TRANSPORT_INFO);
268
269 if (transportInfo == null) throw new AxisFault("No transport info in MessageContext");
270
271 ServletBasedOutTransportInfo servletBasedOutTransportInfo = null;
272 if (transportInfo instanceof ServletBasedOutTransportInfo) {
273 servletBasedOutTransportInfo =
274 (ServletBasedOutTransportInfo) transportInfo;
275
276 // if sending a fault, set HTTP status code to 500
277 if (msgContext.isFault()) {
278 servletBasedOutTransportInfo.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
279 }
280
281 Object customHeaders = msgContext.getProperty(HTTPConstants.HTTP_HEADERS);
282 if (customHeaders != null) {
283 if (customHeaders instanceof List) {
284 Iterator iter = ((List) customHeaders).iterator();
285 while (iter.hasNext()) {
286 Header header = (Header) iter.next();
287 if (header != null) {
288 servletBasedOutTransportInfo
289 .addHeader(header.getName(), header.getValue());
290 }
291 }
292 } else if (customHeaders instanceof Map) {
293 Iterator iter = ((Map) customHeaders).entrySet().iterator();
294 while (iter.hasNext()) {
295 Map.Entry header = (Map.Entry) iter.next();
296 if (header != null) {
297 servletBasedOutTransportInfo
298 .addHeader((String) header.getKey(), (String) header.getValue());
299 }
300 }
301 }
302 }
303 } else if (transportInfo instanceof AxisHttpResponseImpl) {
304 Object customHeaders = msgContext.getProperty(HTTPConstants.HTTP_HEADERS);
305 if (customHeaders != null) {
306 if (customHeaders instanceof List) {
307 Iterator iter = ((List) customHeaders).iterator();
308 while (iter.hasNext()) {
309 Header header = (Header) iter.next();
310 if (header != null) {
311 ((AxisHttpResponseImpl) transportInfo)
312 .addHeader(header.getName(), header.getValue());
313 }
314 }
315 } else if (customHeaders instanceof Map) {
316 Iterator iter = ((Map) customHeaders).entrySet().iterator();
317 while (iter.hasNext()) {
318 Map.Entry header = (Map.Entry) iter.next();
319 if (header != null) {
320 ((AxisHttpResponseImpl) transportInfo)
321 .addHeader((String) header.getKey(), (String) header.getValue());
322 }
323 }
324 }
325 }
326 }
327
328 format.setAutoCloseWriter(true);
329
330 MessageFormatter messageFormatter = TransportUtils.getMessageFormatter(msgContext);
331 if (messageFormatter == null) throw new AxisFault("No MessageFormatter in MessageContext");
332
333 // Once we get to this point, exceptions should NOT be turned into faults and sent,
334 // because we're already sending! So catch everything and log it, but don't pass
335 // upwards.
336
337 try {
338 transportInfo.setContentType(
339 messageFormatter.getContentType(msgContext, format, findSOAPAction(msgContext)));
340
341 Object gzip = msgContext.getOptions().getProperty(HTTPConstants.MC_GZIP_RESPONSE);
342 if (gzip != null && JavaUtils.isTrueExplicitly(gzip)) {
343 if (servletBasedOutTransportInfo != null)
344 servletBasedOutTransportInfo.addHeader(HTTPConstants.HEADER_CONTENT_ENCODING,
345 HTTPConstants.COMPRESSION_GZIP);
346 try {
347 out = new GZIPOutputStream(out);
348 out.write(messageFormatter.getBytes(msgContext, format));
349 ((GZIPOutputStream) out).finish();
350 out.flush();
351 } catch (IOException e) {
352 throw new AxisFault("Could not compress response");
353 }
354 } else {
355 messageFormatter.writeTo(msgContext, format, out, false);
356 }
357 } catch (AxisFault axisFault) {
358 log.error(axisFault.getMessage(), axisFault);
359 throw axisFault;
360 }
361 }
362
363 private void writeMessageWithCommons(MessageContext messageContext,
364 EndpointReference toEPR, OMOutputFormat format)
365 throws AxisFault {
366 try {
367 URL url = new URL(toEPR.getAddress());
368
369 // select the Message Sender depending on the REST status
370 AbstractHTTPSender sender;
371
372 sender = new HTTPSender();
373
374 if (messageContext.getProperty(HTTPConstants.CHUNKED) != null) {
375 chunked = JavaUtils.isTrueExplicitly(messageContext
376 .getProperty(HTTPConstants.CHUNKED));
377 }
378
379 if (messageContext.getProperty(HTTPConstants.HTTP_PROTOCOL_VERSION) != null) {
380 httpVersion = (String) messageContext
381 .getProperty(HTTPConstants.HTTP_PROTOCOL_VERSION);
382 }
383 // Following order needed to be preserved because,
384 // HTTP/1.0 does not support chunk encoding
385 sender.setChunked(chunked);
386 sender.setHttpVersion(httpVersion);
387 sender.setFormat(format);
388
389 sender.send(messageContext, url, findSOAPAction(messageContext));
390 } catch (MalformedURLException e) {
391 log.debug(e);
392 throw AxisFault.makeFault(e);
393 } catch (HttpException e) {
394 log.debug(e);
395 throw AxisFault.makeFault(e);
396 } catch (IOException e) {
397 log.debug(e);
398 throw AxisFault.makeFault(e);
399 }
400 }
401
402 private static String findSOAPAction(MessageContext messageContext) {
403 String soapActionString = null;
404
405 Parameter parameter =
406 messageContext.getTransportOut().getParameter(HTTPConstants.OMIT_SOAP_12_ACTION);
407 if (parameter != null && JavaUtils.isTrueExplicitly(parameter.getValue()) &&
408 !messageContext.isSOAP11()) {
409 return "\"\"";
410 }
411
412 Object disableSoapAction = messageContext.getOptions().getProperty(
413 Constants.Configuration.DISABLE_SOAP_ACTION);
414
415 if (!JavaUtils.isTrueExplicitly(disableSoapAction)) {
416 // first try to get the SOAP action from message context
417 soapActionString = messageContext.getSoapAction();
418 if ((soapActionString == null) || (soapActionString.length() == 0)) {
419 // now let's try to get WSA action
420 soapActionString = messageContext.getWSAAction();
421 if (messageContext.getAxisOperation() != null
422 && ((soapActionString == null) || (soapActionString
423 .length() == 0))) {
424 // last option is to get it from the axis operation
425 soapActionString = messageContext.getAxisOperation()
426 .getSoapAction();
427 }
428 }
429 }
430
431 //Since action is optional for SOAP 1.2 we can return null here.
432 if (soapActionString == null && messageContext.isSOAP11()) {
433 soapActionString = "\"\"";
434 }
435
436 return soapActionString;
437 }
438 }