001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */package org.apache.cxf.transport.http;
019:
020: import java.io.IOException;
021: import java.io.OutputStream;
022: import java.net.HttpURLConnection;
023: import java.util.ArrayList;
024: import java.util.Arrays;
025: import java.util.Enumeration;
026: import java.util.HashMap;
027: import java.util.Iterator;
028: import java.util.List;
029: import java.util.Map;
030: import java.util.logging.Level;
031: import java.util.logging.Logger;
032:
033: import javax.servlet.http.HttpServletRequest;
034: import javax.servlet.http.HttpServletResponse;
035: import javax.xml.namespace.QName;
036:
037: import org.apache.cxf.Bus;
038: import org.apache.cxf.common.logging.LogUtils;
039: import org.apache.cxf.common.util.Base64Exception;
040: import org.apache.cxf.common.util.Base64Utility;
041: import org.apache.cxf.common.util.StringUtils;
042: import org.apache.cxf.configuration.Configurable;
043: import org.apache.cxf.configuration.security.AuthorizationPolicy;
044: import org.apache.cxf.helpers.CastUtils;
045: import org.apache.cxf.helpers.HttpHeaderHelper;
046: import org.apache.cxf.io.AbstractWrappedOutputStream;
047: import org.apache.cxf.message.Exchange;
048: import org.apache.cxf.message.Message;
049: import org.apache.cxf.service.model.EndpointInfo;
050: import org.apache.cxf.transport.AbstractDestination;
051: import org.apache.cxf.transport.AbstractMultiplexDestination;
052: import org.apache.cxf.transport.Conduit;
053: import org.apache.cxf.transport.ConduitInitiator;
054: import org.apache.cxf.transport.http.policy.PolicyUtils;
055: import org.apache.cxf.transports.http.configuration.HTTPServerPolicy;
056: import org.apache.cxf.ws.addressing.EndpointReferenceType;
057: import org.apache.cxf.ws.policy.Assertor;
058: import org.apache.cxf.ws.policy.PolicyEngine;
059: import org.apache.cxf.wsdl.EndpointReferenceUtils;
060:
061: /**
062: * Common base for HTTP Destination implementations.
063: */
064: public abstract class AbstractHTTPDestination extends
065: AbstractMultiplexDestination implements Configurable, Assertor {
066:
067: public static final String HTTP_REQUEST = "HTTP.REQUEST";
068: public static final String HTTP_RESPONSE = "HTTP.RESPONSE";
069: public static final String HTTP_CONTEXT = "HTTP.CONTEXT";
070: public static final String PROTOCOL_HEADERS_CONTENT_TYPE = Message.CONTENT_TYPE
071: .toLowerCase();
072:
073: private static final Logger LOG = LogUtils
074: .getL7dLogger(AbstractHTTPDestination.class);
075:
076: private static final long serialVersionUID = 1L;
077:
078: protected final Bus bus;
079: protected final ConduitInitiator conduitInitiator;
080:
081: // Configuration values
082: protected HTTPServerPolicy server;
083: protected String contextMatchStrategy = "stem";
084: protected boolean fixedParameterOrder;
085: protected boolean multiplexWithAddress;
086:
087: /**
088: * Constructor
089: *
090: * @param b the associated Bus
091: * @param ci the associated conduit initiator
092: * @param ei the endpoint info of the destination
093: * @param dp ture for adding the default port if it is missing
094: * @throws IOException
095: */
096: public AbstractHTTPDestination(Bus b, ConduitInitiator ci,
097: EndpointInfo ei, boolean dp) throws IOException {
098: super (b, getTargetReference(getAddressValue(ei, dp), b), ei);
099: bus = b;
100: conduitInitiator = ci;
101:
102: initConfig();
103: }
104:
105: /**
106: * Cache HTTP headers in message.
107: *
108: * @param message the current message
109: */
110: protected void setHeaders(Message message) {
111: Map<String, List<String>> requestHeaders = new HashMap<String, List<String>>();
112: copyRequestHeaders(message, requestHeaders);
113: message.put(Message.PROTOCOL_HEADERS, requestHeaders);
114:
115: if (requestHeaders.containsKey("Authorization")) {
116: List<String> authorizationLines = requestHeaders
117: .get("Authorization");
118: String credentials = authorizationLines.get(0);
119: String authType = credentials.split(" ")[0];
120: if ("Basic".equals(authType)) {
121: String authEncoded = credentials.split(" ")[1];
122: try {
123: String authDecoded = new String(Base64Utility
124: .decode(authEncoded));
125: String authInfo[] = authDecoded.split(":");
126: String username = authInfo[0];
127: String password = authInfo[1];
128:
129: AuthorizationPolicy policy = new AuthorizationPolicy();
130: policy.setUserName(username);
131: policy.setPassword(password);
132:
133: message.put(AuthorizationPolicy.class, policy);
134: } catch (Base64Exception ex) {
135: //ignore, we'll leave things alone. They can try decoding it themselves
136: }
137: }
138: }
139:
140: }
141:
142: protected void updateResponseHeaders(Message message) {
143: Map<String, List<String>> responseHeaders = CastUtils
144: .cast((Map) message.get(Message.PROTOCOL_HEADERS));
145: if (responseHeaders == null) {
146: responseHeaders = new HashMap<String, List<String>>();
147: message.put(Message.PROTOCOL_HEADERS, responseHeaders);
148: }
149: setPolicies(responseHeaders);
150: }
151:
152: /**
153: * @param message the message under consideration
154: * @return true iff the message has been marked as oneway
155: */
156: protected final boolean isOneWay(Message message) {
157: Exchange ex = message.getExchange();
158: return ex == null ? false : ex.isOneWay();
159: }
160:
161: /**
162: * Copy the request headers into the message.
163: *
164: * @param message the current message
165: * @param headers the current set of headers
166: */
167: protected void copyRequestHeaders(Message message,
168: Map<String, List<String>> headers) {
169: HttpServletRequest req = (HttpServletRequest) message
170: .get(HTTP_REQUEST);
171: //TODO how to deal with the fields
172: for (Enumeration e = req.getHeaderNames(); e.hasMoreElements();) {
173: String fname = (String) e.nextElement();
174: List<String> values;
175: if (headers.containsKey(fname)) {
176: values = headers.get(fname);
177: } else {
178: values = new ArrayList<String>();
179: headers.put(HttpHeaderHelper.getHeaderKey(fname),
180: values);
181: }
182: for (Enumeration e2 = req.getHeaders(fname); e2
183: .hasMoreElements();) {
184: String val = (String) e2.nextElement();
185: values.add(val);
186: }
187: }
188: }
189:
190: /**
191: * Copy the response headers into the response.
192: *
193: * @param message the current message
194: * @param headers the current set of headers
195: */
196: protected void copyResponseHeaders(Message message,
197: HttpServletResponse response) {
198: String ct = (String) message.get(Message.CONTENT_TYPE);
199: String enc = (String) message.get(Message.ENCODING);
200:
201: if (null != ct && null != enc && ct.indexOf("charset=") == -1) {
202: ct = ct + "; charset=" + enc;
203: }
204:
205: Map<?, ?> headers = (Map<?, ?>) message
206: .get(Message.PROTOCOL_HEADERS);
207: if (null != headers) {
208:
209: if (!headers.containsKey(Message.CONTENT_TYPE)) {
210: response.setContentType(ct);
211: }
212:
213: for (Iterator<?> iter = headers.keySet().iterator(); iter
214: .hasNext();) {
215: String header = (String) iter.next();
216: List<?> headerList = (List<?>) headers.get(header);
217: for (Object value : headerList) {
218: response.addHeader(header, (String) value);
219: }
220: }
221: } else {
222: response.setContentType(ct);
223: }
224: }
225:
226: protected static EndpointInfo getAddressValue(EndpointInfo ei) {
227: return getAddressValue(ei, true);
228: }
229:
230: protected static EndpointInfo getAddressValue(EndpointInfo ei,
231: boolean dp) {
232: if (dp) {
233: String addr = StringUtils.addDefaultPortIfMissing(ei
234: .getAddress());
235: if (addr != null) {
236: ei.setAddress(addr);
237: }
238: }
239: return ei;
240: }
241:
242: /**
243: * @param inMessage the incoming message
244: * @return the inbuilt backchannel
245: */
246: protected Conduit getInbuiltBackChannel(Message inMessage) {
247: HttpServletResponse response = (HttpServletResponse) inMessage
248: .get(HTTP_RESPONSE);
249: return new BackChannelConduit(response);
250: }
251:
252: /**
253: * Mark message as a partial message.
254: *
255: * @param partialResponse the partial response message
256: * @param the decoupled target
257: * @return true iff partial responses are supported
258: */
259: protected boolean markPartialResponse(Message partialResponse,
260: EndpointReferenceType decoupledTarget) {
261: // setup the outbound message to for 202 Accepted
262: partialResponse.put(Message.RESPONSE_CODE,
263: HttpURLConnection.HTTP_ACCEPTED);
264: partialResponse.getExchange().put(EndpointReferenceType.class,
265: decoupledTarget);
266: return true;
267: }
268:
269: private void initConfig() {
270: PolicyEngine engine = bus.getExtension(PolicyEngine.class);
271: // for a decoupled endpoint there is no service info
272: if (null != engine && engine.isEnabled()
273: && null != endpointInfo.getService()) {
274: server = PolicyUtils.getServer(engine, endpointInfo, this );
275: }
276: if (null == server) {
277: server = endpointInfo.getTraversedExtensor(
278: new HTTPServerPolicy(), HTTPServerPolicy.class);
279: }
280: }
281:
282: void setPolicies(Map<String, List<String>> headers) {
283: HTTPServerPolicy policy = server;
284: if (policy.isSetCacheControl()) {
285: headers.put("Cache-Control", Arrays
286: .asList(new String[] { policy.getCacheControl()
287: .value() }));
288: }
289: if (policy.isSetContentLocation()) {
290: headers.put("Content-Location",
291: Arrays.asList(new String[] { policy
292: .getContentLocation() }));
293: }
294: if (policy.isSetContentEncoding()) {
295: headers.put("Content-Encoding",
296: Arrays.asList(new String[] { policy
297: .getContentEncoding() }));
298: }
299: if (policy.isSetContentType()) {
300: headers.put(HttpHeaderHelper.CONTENT_TYPE, Arrays
301: .asList(new String[] { policy.getContentType() }));
302: }
303: if (policy.isSetServerType()) {
304: headers.put("Server", Arrays.asList(new String[] { policy
305: .getServerType() }));
306: }
307: if (policy.isSetHonorKeepAlive() && !policy.isHonorKeepAlive()) {
308: headers.put("Connection", Arrays
309: .asList(new String[] { "close" }));
310: }
311:
312: /*
313: * TODO - hook up these policies
314: <xs:attribute name="SuppressClientSendErrors" type="xs:boolean" use="optional" default="false">
315: <xs:attribute name="SuppressClientReceiveErrors" type="xs:boolean" use="optional" default="false">
316: */
317: }
318:
319: protected OutputStream flushHeaders(Message outMessage)
320: throws IOException {
321: updateResponseHeaders(outMessage);
322: Object responseObj = outMessage.get(HTTP_RESPONSE);
323: OutputStream responseStream = null;
324: boolean oneWay = isOneWay(outMessage);
325: if (responseObj instanceof HttpServletResponse) {
326: HttpServletResponse response = (HttpServletResponse) responseObj;
327:
328: Integer i = (Integer) outMessage.get(Message.RESPONSE_CODE);
329: if (i != null) {
330: int status = i.intValue();
331: if (HttpURLConnection.HTTP_INTERNAL_ERROR == i) {
332: Map<Object, Object> pHeaders = CastUtils
333: .cast((Map) outMessage
334: .get(Message.PROTOCOL_HEADERS));
335: if (null != pHeaders
336: && pHeaders
337: .containsKey(PROTOCOL_HEADERS_CONTENT_TYPE)) {
338: pHeaders.remove(PROTOCOL_HEADERS_CONTENT_TYPE);
339: }
340: }
341: response.setStatus(status);
342: } else {
343: response.setStatus(HttpURLConnection.HTTP_OK);
344: }
345:
346: copyResponseHeaders(outMessage, response);
347: responseStream = response.getOutputStream();
348:
349: if (oneWay) {
350: response.flushBuffer();
351: }
352: } else if (null != responseObj) {
353: String m = (new org.apache.cxf.common.i18n.Message(
354: "UNEXPECTED_RESPONSE_TYPE_MSG", LOG, responseObj
355: .getClass())).toString();
356: LOG.log(Level.WARNING, m);
357: throw new IOException(m);
358: } else {
359: String m = (new org.apache.cxf.common.i18n.Message(
360: "NULL_RESPONSE_MSG", LOG)).toString();
361: LOG.log(Level.WARNING, m);
362: throw new IOException(m);
363: }
364:
365: if (oneWay) {
366: outMessage.remove(HTTP_RESPONSE);
367: }
368: return responseStream;
369: }
370:
371: /**
372: * Backchannel conduit.
373: */
374: public class BackChannelConduit extends
375: AbstractDestination.AbstractBackChannelConduit {
376:
377: protected HttpServletResponse response;
378:
379: BackChannelConduit(HttpServletResponse resp) {
380: response = resp;
381: }
382:
383: /**
384: * Send an outbound message, assumed to contain all the name-value
385: * mappings of the corresponding input message (if any).
386: *
387: * @param message the message to be sent.
388: */
389: public void prepare(Message message) throws IOException {
390: message.put(HTTP_RESPONSE, response);
391: message.setContent(OutputStream.class,
392: new WrappedOutputStream(message, response));
393: }
394: }
395:
396: /**
397: * Wrapper stream responsible for flushing headers and committing outgoing
398: * HTTP-level response.
399: */
400: private class WrappedOutputStream extends
401: AbstractWrappedOutputStream {
402:
403: protected HttpServletResponse response;
404: private Message outMessage;
405:
406: WrappedOutputStream(Message m, HttpServletResponse resp) {
407: super ();
408: this .outMessage = m;
409: response = resp;
410: }
411:
412: /**
413: * Perform any actions required on stream flush (freeze headers,
414: * reset output stream ... etc.)
415: */
416: protected void onFirstWrite() throws IOException {
417: OutputStream responseStream = flushHeaders(outMessage);
418: if (null != responseStream) {
419: wrappedStream = responseStream;
420: }
421: }
422:
423: /**
424: * Perform any actions required on stream closure (handle response etc.)
425: */
426: public void close() throws IOException {
427: if (wrappedStream == null) {
428: OutputStream responseStream = flushHeaders(outMessage);
429: if (null != responseStream) {
430: wrappedStream = responseStream;
431: }
432: }
433: wrappedStream.close();
434: response.flushBuffer();
435: }
436:
437: public void flush() throws IOException {
438: //ignore until we close
439: // or we'll force chunking and cause all kinds of network packets
440: }
441: }
442:
443: protected boolean contextMatchOnExact() {
444: return "exact".equals(contextMatchStrategy);
445: }
446:
447: public String getBeanName() {
448: String beanName = null;
449: if (endpointInfo.getName() != null) {
450: beanName = endpointInfo.getName().toString()
451: + ".http-destination";
452: }
453: return beanName;
454: }
455:
456: /*
457: * Implement multiplex via the address URL to avoid the need for ws-a.
458: * Requires contextMatchStrategy of stem.
459: *
460: * @see org.apache.cxf.transport.AbstractMultiplexDestination#getAddressWithId(java.lang.String)
461: */
462: public EndpointReferenceType getAddressWithId(String id) {
463: EndpointReferenceType ref = null;
464:
465: if (isMultiplexWithAddress()) {
466: String address = EndpointReferenceUtils
467: .getAddress(reference);
468: ref = EndpointReferenceUtils.duplicate(reference);
469: if (address.endsWith("/")) {
470: EndpointReferenceUtils.setAddress(ref, address + id);
471: } else {
472: EndpointReferenceUtils.setAddress(ref, address + "/"
473: + id);
474: }
475: } else {
476: ref = super .getAddressWithId(id);
477: }
478: return ref;
479: }
480:
481: /*
482: * (non-Javadoc)
483: *
484: * @see org.apache.cxf.transport.AbstractMultiplexDestination#getId(java.util.Map)
485: */
486: public String getId(Map context) {
487: String id = null;
488:
489: if (isMultiplexWithAddress()) {
490: String address = (String) context.get(Message.PATH_INFO);
491: if (null != address) {
492: int afterLastSlashIndex = address.lastIndexOf("/") + 1;
493: if (afterLastSlashIndex > 0
494: && afterLastSlashIndex < address.length()) {
495: id = address.substring(afterLastSlashIndex);
496: }
497: } else {
498: getLogger().log(
499: Level.WARNING,
500: new org.apache.cxf.common.i18n.Message(
501: "MISSING_PATH_INFO", LOG).toString());
502: }
503: } else {
504: return super .getId(context);
505: }
506: return id;
507: }
508:
509: public String getContextMatchStrategy() {
510: return contextMatchStrategy;
511: }
512:
513: public void setContextMatchStrategy(String contextMatchStrategy) {
514: this .contextMatchStrategy = contextMatchStrategy;
515: }
516:
517: public boolean isFixedParameterOrder() {
518: return fixedParameterOrder;
519: }
520:
521: public void setFixedParameterOrder(boolean fixedParameterOrder) {
522: this .fixedParameterOrder = fixedParameterOrder;
523: }
524:
525: public boolean isMultiplexWithAddress() {
526: return multiplexWithAddress;
527: }
528:
529: public void setMultiplexWithAddress(boolean multiplexWithAddress) {
530: this .multiplexWithAddress = multiplexWithAddress;
531: }
532:
533: public HTTPServerPolicy getServer() {
534: return server;
535: }
536:
537: public void setServer(HTTPServerPolicy server) {
538: this .server = server;
539: }
540:
541: public void assertMessage(Message message) {
542: PolicyUtils.assertServerPolicy(message, server);
543: }
544:
545: public boolean canAssert(QName type) {
546: return PolicyUtils.HTTPSERVERPOLICY_ASSERTION_QNAME
547: .equals(type);
548: }
549:
550: }
|