Wed, 27 Apr 2016 01:27:09 +0800
Initial load
http://hg.openjdk.java.net/jdk8u/jdk8u/jaxws/
changeset: 657:d47a47f961ee
tag: jdk8u25-b17
1 /*
2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
26 package com.sun.xml.internal.ws.transport.http;
28 import java.io.ByteArrayOutputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.io.OutputStreamWriter;
33 import java.io.PrintWriter;
34 import java.net.HttpURLConnection;
35 import java.util.Collections;
36 import java.util.HashMap;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Map.Entry;
40 import java.util.TreeMap;
41 import java.util.logging.Level;
42 import java.util.logging.Logger;
44 import javax.xml.ws.Binding;
45 import javax.xml.ws.WebServiceException;
46 import javax.xml.ws.http.HTTPBinding;
48 import com.oracle.webservices.internal.api.message.PropertySet;
49 import com.sun.istack.internal.NotNull;
50 import com.sun.istack.internal.Nullable;
51 import com.sun.xml.internal.ws.api.Component;
52 import com.sun.xml.internal.ws.api.EndpointAddress;
53 import com.sun.xml.internal.ws.api.SOAPVersion;
54 import com.sun.xml.internal.ws.api.addressing.AddressingVersion;
55 import com.sun.xml.internal.ws.api.addressing.NonAnonymousResponseProcessor;
56 import com.sun.xml.internal.ws.api.ha.HaInfo;
57 import com.sun.xml.internal.ws.api.message.ExceptionHasMessage;
58 import com.sun.xml.internal.ws.api.message.Message;
59 import com.sun.xml.internal.ws.api.message.Packet;
60 import com.sun.xml.internal.ws.api.pipe.Codec;
61 import com.sun.xml.internal.ws.api.pipe.ContentType;
62 import com.sun.xml.internal.ws.api.server.AbstractServerAsyncTransport;
63 import com.sun.xml.internal.ws.api.server.Adapter;
64 import com.sun.xml.internal.ws.api.server.BoundEndpoint;
65 import com.sun.xml.internal.ws.api.server.DocumentAddressResolver;
66 import com.sun.xml.internal.ws.api.server.Module;
67 import com.sun.xml.internal.ws.api.server.PortAddressResolver;
68 import com.sun.xml.internal.ws.api.server.SDDocument;
69 import com.sun.xml.internal.ws.api.server.ServiceDefinition;
70 import com.sun.xml.internal.ws.api.server.TransportBackChannel;
71 import com.sun.xml.internal.ws.api.server.WSEndpoint;
72 import com.sun.xml.internal.ws.api.server.WebServiceContextDelegate;
73 import com.sun.xml.internal.ws.fault.SOAPFaultBuilder;
74 import com.sun.xml.internal.ws.resources.WsservletMessages;
75 import com.sun.xml.internal.ws.server.UnsupportedMediaException;
76 import com.sun.xml.internal.ws.util.ByteArrayBuffer;
77 import com.sun.xml.internal.ws.util.Pool;
80 /**
81 * {@link com.sun.xml.internal.ws.api.server.Adapter} that receives messages in HTTP.
82 *
83 * <p>
84 * This object also assigns unique query string (such as "xsd=1") to
85 * each {@link com.sun.xml.internal.ws.api.server.SDDocument} so that they can be served by HTTP GET requests.
86 *
87 * @author Kohsuke Kawaguchi
88 * @author Jitendra Kotamraju
89 */
90 public class HttpAdapter extends Adapter<HttpAdapter.HttpToolkit> {
92 private static final Logger LOGGER = Logger.getLogger(HttpAdapter.class.getName());
94 /**
95 * {@link com.sun.xml.internal.ws.api.server.SDDocument}s keyed by the query string like "?abc".
96 * Used for serving documents via HTTP GET.
97 *
98 * Empty if the endpoint doesn't have {@link com.sun.xml.internal.ws.api.server.ServiceDefinition}.
99 * Read-only.
100 */
101 protected Map<String,SDDocument> wsdls;
103 /**
104 * Reverse map of {@link #wsdls}. Read-only.
105 */
106 private Map<SDDocument,String> revWsdls;
108 /**
109 * A reference to the service definition from which the map of wsdls/revWsdls
110 * was created. This allows us to establish if the service definition documents
111 * have changed in the meantime.
112 */
113 private ServiceDefinition serviceDefinition = null;
115 public final HttpAdapterList<? extends HttpAdapter> owner;
117 /**
118 * Servlet URL pattern with which this {@link com.sun.xml.internal.ws.transport.http.HttpAdapter} is associated.
119 */
120 public final String urlPattern;
122 protected boolean stickyCookie;
124 protected boolean disableJreplicaCookie = false;
126 /**
127 * Creates a lone {@link com.sun.xml.internal.ws.transport.http.HttpAdapter} that does not know of any other
128 * {@link com.sun.xml.internal.ws.transport.http.HttpAdapter}s.
129 *
130 * This is convenient for creating an {@link com.sun.xml.internal.ws.transport.http.HttpAdapter} for an environment
131 * where they don't know each other (such as JavaSE deployment.)
132 *
133 * @param endpoint web service endpoint
134 * @return singe adapter to process HTTP messages
135 */
136 public static HttpAdapter createAlone(WSEndpoint endpoint) {
137 return new DummyList().createAdapter("","",endpoint);
138 }
140 /**
141 * @deprecated
142 * remove as soon as we can update the test util.
143 * @param endpoint web service endpoint
144 * @param owner list of related adapters
145 */
146 protected HttpAdapter(WSEndpoint endpoint,
147 HttpAdapterList<? extends HttpAdapter> owner) {
148 this(endpoint,owner,null);
149 }
151 protected HttpAdapter(WSEndpoint endpoint,
152 HttpAdapterList<? extends HttpAdapter> owner,
153 String urlPattern) {
154 super(endpoint);
155 this.owner = owner;
156 this.urlPattern = urlPattern;
158 initWSDLMap(endpoint.getServiceDefinition());
159 }
161 /**
162 * Return the last known service definition of the endpoint.
163 *
164 * @return The service definition of the endpoint
165 */
166 public ServiceDefinition getServiceDefinition() {
167 return this.serviceDefinition;
168 }
170 /**
171 * Fill in WSDL map.
172 *
173 * @param sdef service definition
174 */
175 public final void initWSDLMap(ServiceDefinition sdef) {
176 this.serviceDefinition = sdef;
177 if(sdef==null) {
178 wsdls = Collections.emptyMap();
179 revWsdls = Collections.emptyMap();
180 } else {
181 wsdls = new HashMap<String, SDDocument>(); // wsdl=1 --> Doc
182 // Sort WSDL, Schema documents based on SystemId so that the same
183 // document gets wsdl=x mapping
184 Map<String, SDDocument> systemIds = new TreeMap<String, SDDocument>();
185 for (SDDocument sdd : sdef) {
186 if (sdd == sdef.getPrimary()) { // No sorting for Primary WSDL
187 wsdls.put("wsdl", sdd);
188 wsdls.put("WSDL", sdd);
189 } else {
190 systemIds.put(sdd.getURL().toString(), sdd);
191 }
192 }
194 int wsdlnum = 1;
195 int xsdnum = 1;
196 for (Entry<String, SDDocument> e : systemIds.entrySet()) {
197 SDDocument sdd = e.getValue();
198 if (sdd.isWSDL()) {
199 wsdls.put("wsdl="+(wsdlnum++),sdd);
200 }
201 if (sdd.isSchema()) {
202 wsdls.put("xsd="+(xsdnum++),sdd);
203 }
204 }
206 revWsdls = new HashMap<SDDocument,String>(); // Doc --> wsdl=1
207 for (Entry<String,SDDocument> e : wsdls.entrySet()) {
208 if (!e.getKey().equals("WSDL")) { // map Doc --> wsdl, not WSDL
209 revWsdls.put(e.getValue(),e.getKey());
210 }
211 }
212 }
213 }
215 /**
216 * Returns the "/abc/def/ghi" portion if
217 * the URL pattern is "/abc/def/ghi/*".
218 */
219 public String getValidPath() {
220 if (urlPattern.endsWith("/*")) {
221 return urlPattern.substring(0, urlPattern.length() - 2);
222 } else {
223 return urlPattern;
224 }
225 }
227 @Override
228 protected HttpToolkit createToolkit() {
229 return new HttpToolkit();
230 }
232 /**
233 * Receives the incoming HTTP connection and dispatches
234 * it to JAX-WS. This method returns when JAX-WS completes
235 * processing the request and the whole reply is written
236 * to {@link WSHTTPConnection}.
237 *
238 * <p>
239 * This method is invoked by the lower-level HTTP stack,
240 * and "connection" here is an HTTP connection.
241 *
242 * <p>
243 * To populate a request {@link com.sun.xml.internal.ws.api.message.Packet} with more info,
244 * define {@link com.oracle.webservices.internal.api.message.PropertySet.Property properties} on
245 * {@link WSHTTPConnection}.
246 *
247 * @param connection to receive/send HTTP messages for web service endpoints
248 * @throws java.io.IOException when I/O errors happen
249 */
250 public void handle(@NotNull WSHTTPConnection connection) throws IOException {
251 if (handleGet(connection)) {
252 return;
253 }
255 // Make sure the Toolkit is recycled by the same pool instance from which it was taken
256 final Pool<HttpToolkit> currentPool = getPool();
257 // normal request handling
258 final HttpToolkit tk = currentPool.take();
259 try {
260 tk.handle(connection);
261 } finally {
262 currentPool.recycle(tk);
263 }
264 }
266 public boolean handleGet(@NotNull WSHTTPConnection connection) throws IOException {
267 if (connection.getRequestMethod().equals("GET")) {
268 // metadata query. let the interceptor run
269 for (Component c : endpoint.getComponents()) {
270 HttpMetadataPublisher spi = c.getSPI(HttpMetadataPublisher.class);
271 if (spi != null && spi.handleMetadataRequest(this, connection)) {
272 return true;
273 } // handled
274 }
276 if (isMetadataQuery(connection.getQueryString())) {
277 // Sends published WSDL and schema documents as the default action.
278 publishWSDL(connection);
279 return true;
280 }
282 Binding binding = getEndpoint().getBinding();
283 if (!(binding instanceof HTTPBinding)) {
284 // Writes HTML page with all the endpoint descriptions
285 writeWebServicesHtmlPage(connection);
286 return true;
287 }
288 } else if (connection.getRequestMethod().equals("HEAD")) {
289 connection.getInput().close();
290 Binding binding = getEndpoint().getBinding();
291 if (isMetadataQuery(connection.getQueryString())) {
292 SDDocument doc = wsdls.get(connection.getQueryString());
293 connection.setStatus(doc != null
294 ? HttpURLConnection.HTTP_OK
295 : HttpURLConnection.HTTP_NOT_FOUND);
296 connection.getOutput().close();
297 connection.close();
298 return true;
299 } else if (!(binding instanceof HTTPBinding)) {
300 connection.setStatus(HttpURLConnection.HTTP_NOT_FOUND);
301 connection.getOutput().close();
302 connection.close();
303 return true;
304 }
305 // Let the endpoint handle for HTTPBinding
306 }
308 return false;
310 }
311 /*
312 *
313 * @param con
314 * @param codec
315 * @return
316 * @throws IOException
317 * ExceptionHasMessage exception that contains particular fault message
318 * UnsupportedMediaException to indicate to send 415 error code
319 */
320 private Packet decodePacket(@NotNull WSHTTPConnection con, @NotNull Codec codec) throws IOException {
321 String ct = con.getRequestHeader("Content-Type");
322 InputStream in = con.getInput();
323 Packet packet = new Packet();
324 packet.soapAction = fixQuotesAroundSoapAction(con.getRequestHeader("SOAPAction"));
325 packet.wasTransportSecure = con.isSecure();
326 packet.acceptableMimeTypes = con.getRequestHeader("Accept");
327 packet.addSatellite(con);
328 addSatellites(packet);
329 packet.isAdapterDeliversNonAnonymousResponse = true;
330 packet.component = this;
331 packet.transportBackChannel = new Oneway(con);
332 packet.webServiceContextDelegate = con.getWebServiceContextDelegate();
333 packet.setState(Packet.State.ServerRequest);
334 if (dump || LOGGER.isLoggable(Level.FINER)) {
335 ByteArrayBuffer buf = new ByteArrayBuffer();
336 buf.write(in);
337 in.close();
338 dump(buf, "HTTP request", con.getRequestHeaders());
339 in = buf.newInputStream();
340 }
341 codec.decode(in, ct, packet);
342 return packet;
343 }
345 protected void addSatellites(Packet packet) {
346 }
348 /**
349 * Some stacks may send non WS-I BP 1.2 conforming SoapAction.
350 * Make sure SOAPAction is quoted as {@link com.sun.xml.internal.ws.api.message.Packet#soapAction} expects quoted soapAction value.
351 *
352 * @param soapAction SoapAction HTTP Header
353 * @return quoted SOAPAction value
354 */
355 static public String fixQuotesAroundSoapAction(String soapAction) {
356 if(soapAction != null && (!soapAction.startsWith("\"") || !soapAction.endsWith("\"")) ) {
357 if (LOGGER.isLoggable(Level.INFO)) {
358 LOGGER.log(Level.INFO, "Received WS-I BP non-conformant Unquoted SoapAction HTTP header: {0}", soapAction);
359 }
360 String fixedSoapAction = soapAction;
361 if(!soapAction.startsWith("\"")) {
362 fixedSoapAction = "\"" + fixedSoapAction;
363 }
364 if(!soapAction.endsWith("\"")) {
365 fixedSoapAction = fixedSoapAction + "\"";
366 }
367 return fixedSoapAction;
368 }
369 return soapAction;
370 }
372 protected NonAnonymousResponseProcessor getNonAnonymousResponseProcessor() {
373 return NonAnonymousResponseProcessor.getDefault();
374 }
376 /**
377 * This method is added for the case of the sub-class wants to override the method to
378 * print details. E.g. convert soapfault as HTML msg for 403 error connstatus.
379 * @param os
380 */
381 protected void writeClientError(int connStatus, @NotNull OutputStream os, @NotNull Packet packet) throws IOException {
382 //do nothing
383 }
385 private boolean isClientErrorStatus(int connStatus)
386 {
387 return (connStatus == HttpURLConnection.HTTP_FORBIDDEN); // add more for future.
388 }
390 private boolean isNonAnonymousUri(EndpointAddress addr){
391 return (addr != null) && !addr.toString().equals(AddressingVersion.W3C.anonymousUri) &&
392 !addr.toString().equals(AddressingVersion.MEMBER.anonymousUri);
393 }
395 private void encodePacket(@NotNull Packet packet, @NotNull WSHTTPConnection con, @NotNull Codec codec) throws IOException {
396 if (isNonAnonymousUri(packet.endpointAddress) && packet.getMessage() != null) {
397 try {
398 // Message is targeted to non-anonymous response endpoint.
399 // After call to non-anonymous processor, typically, packet.getMessage() will be null
400 // however, processors could use this pattern to modify the response sent on the back-channel,
401 // e.g. send custom HTTP headers with the HTTP 202
402 packet = getNonAnonymousResponseProcessor().process(packet);
403 } catch (RuntimeException re) {
404 // if processing by NonAnonymousResponseProcessor fails, new SOAPFaultMessage is created to be sent
405 // to back-channel client
406 SOAPVersion soapVersion = packet.getBinding().getSOAPVersion();
407 Message faultMsg = SOAPFaultBuilder.createSOAPFaultMessage(soapVersion, null, re);
408 packet = packet.createServerResponse(faultMsg, packet.endpoint.getPort(), null, packet.endpoint.getBinding());
409 }
410 }
412 if (con.isClosed()) {
413 return; // Connection is already closed
414 }
415 Message responseMessage = packet.getMessage();
416 addStickyCookie(con);
417 addReplicaCookie(con, packet);
418 if (responseMessage == null) {
419 if (!con.isClosed()) {
420 // set the response code if not already set
421 // for example, 415 may have been set earlier for Unsupported Content-Type
422 if (con.getStatus() == 0) {
423 con.setStatus(WSHTTPConnection.ONEWAY);
424 }
425 OutputStream os = con.getProtocol().contains("1.1") ? con.getOutput() : new Http10OutputStream(con);
426 if (dump || LOGGER.isLoggable(Level.FINER)) {
427 ByteArrayBuffer buf = new ByteArrayBuffer();
428 codec.encode(packet, buf);
429 dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders());
430 buf.writeTo(os);
431 } else {
432 codec.encode(packet, os);
433 }
434 // close the response channel now
435 try {
436 os.close(); // no payload
437 } catch (IOException e) {
438 throw new WebServiceException(e);
439 }
440 }
441 } else {
442 if (con.getStatus() == 0) {
443 // if the appliation didn't set the status code,
444 // set the default one.
445 con.setStatus(responseMessage.isFault()
446 ? HttpURLConnection.HTTP_INTERNAL_ERROR
447 : HttpURLConnection.HTTP_OK);
448 }
450 if (isClientErrorStatus(con.getStatus())) {
451 OutputStream os = con.getOutput();
452 if (dump || LOGGER.isLoggable(Level.FINER)) {
453 ByteArrayBuffer buf = new ByteArrayBuffer();
454 writeClientError(con.getStatus(), buf, packet);
455 dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders());
456 buf.writeTo(os);
457 } else {
458 writeClientError(con.getStatus(), os, packet);
459 }
460 os.close();
461 return;
462 }
464 ContentType contentType = codec.getStaticContentType(packet);
465 if (contentType != null) {
466 con.setContentTypeResponseHeader(contentType.getContentType());
467 OutputStream os = con.getProtocol().contains("1.1") ? con.getOutput() : new Http10OutputStream(con);
468 if (dump || LOGGER.isLoggable(Level.FINER)) {
469 ByteArrayBuffer buf = new ByteArrayBuffer();
470 codec.encode(packet, buf);
471 dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders());
472 buf.writeTo(os);
473 } else {
474 codec.encode(packet, os);
475 }
476 os.close();
477 } else {
479 ByteArrayBuffer buf = new ByteArrayBuffer();
480 contentType = codec.encode(packet, buf);
481 con.setContentTypeResponseHeader(contentType.getContentType());
482 if (dump || LOGGER.isLoggable(Level.FINER)) {
483 dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders());
484 }
485 OutputStream os = con.getOutput();
486 buf.writeTo(os);
487 os.close();
488 }
489 }
490 }
492 /*
493 * GlassFish Load-balancer plugin always add a header proxy-jroute on
494 * request being send from load-balancer plugin to server
495 *
496 * JROUTE cookie need to be stamped in two cases
497 * 1 : At the time of session creation. In this case, request will not have
498 * any JROUTE cookie.
499 * 2 : At the time of fail-over. In this case, value of proxy-jroute
500 * header(will point to current instance) and JROUTE cookie(will point to
501 * previous failed instance) will be different. This logic can be used
502 * to determine fail-over scenario.
503 */
504 private void addStickyCookie(WSHTTPConnection con) {
505 if (stickyCookie) {
506 String proxyJroute = con.getRequestHeader("proxy-jroute");
507 if (proxyJroute == null) {
508 // Load-balancer plugin is not front-ending this instance
509 return;
510 }
512 String jrouteId = con.getCookie("JROUTE");
513 if (jrouteId == null || !jrouteId.equals(proxyJroute)) {
514 // Initial request or failover
515 con.setCookie("JROUTE", proxyJroute);
516 }
517 }
518 }
520 private void addReplicaCookie(WSHTTPConnection con, Packet packet) {
521 if (stickyCookie) {
522 HaInfo haInfo = null;
523 if (packet.supports(Packet.HA_INFO)) {
524 haInfo = (HaInfo)packet.get(Packet.HA_INFO);
525 }
526 if (haInfo != null) {
527 con.setCookie("METRO_KEY", haInfo.getKey());
528 if (!disableJreplicaCookie) {
529 con.setCookie("JREPLICA", haInfo.getReplicaInstance());
530 }
531 }
532 }
533 }
535 public void invokeAsync(final WSHTTPConnection con) throws IOException {
536 invokeAsync(con, NO_OP_COMPLETION_CALLBACK);
537 }
539 public void invokeAsync(final WSHTTPConnection con, final CompletionCallback callback) throws IOException {
541 if (handleGet(con)) {
542 callback.onCompletion();
543 return;
544 }
545 final Pool<HttpToolkit> currentPool = getPool();
546 final HttpToolkit tk = currentPool.take();
547 final Packet request;
549 try {
551 request = decodePacket(con, tk.codec);
552 } catch (ExceptionHasMessage e) {
553 LOGGER.log(Level.SEVERE, e.getMessage(), e);
554 Packet response = new Packet();
555 response.setMessage(e.getFaultMessage());
556 encodePacket(response, con, tk.codec);
557 currentPool.recycle(tk);
558 con.close();
559 callback.onCompletion();
560 return;
561 } catch (UnsupportedMediaException e) {
562 LOGGER.log(Level.SEVERE, e.getMessage(), e);
563 Packet response = new Packet();
564 con.setStatus(WSHTTPConnection.UNSUPPORTED_MEDIA);
565 encodePacket(response, con, tk.codec);
566 currentPool.recycle(tk);
567 con.close();
568 callback.onCompletion();
569 return;
570 }
572 endpoint.process(request, new WSEndpoint.CompletionCallback() {
573 @Override
574 public void onCompletion(@NotNull Packet response) {
575 try {
576 try {
577 encodePacket(response, con, tk.codec);
578 } catch (IOException ioe) {
579 LOGGER.log(Level.SEVERE, ioe.getMessage(), ioe);
580 }
581 currentPool.recycle(tk);
582 } finally {
583 con.close();
584 callback.onCompletion();
586 }
587 }
588 },null);
590 }
592 public static final CompletionCallback NO_OP_COMPLETION_CALLBACK = new CompletionCallback() {
594 @Override
595 public void onCompletion() {
596 //NO-OP
597 }
598 };
600 public interface CompletionCallback{
601 void onCompletion();
602 }
604 final class AsyncTransport extends AbstractServerAsyncTransport<WSHTTPConnection> {
606 public AsyncTransport() {
607 super(endpoint);
608 }
610 public void handleAsync(WSHTTPConnection con) throws IOException {
611 super.handle(con);
612 }
614 @Override
615 protected void encodePacket(WSHTTPConnection con, @NotNull Packet packet, @NotNull Codec codec) throws IOException {
616 HttpAdapter.this.encodePacket(packet, con, codec);
617 }
619 protected @Override @Nullable String getAcceptableMimeTypes(WSHTTPConnection con) {
620 return null;
621 }
623 protected @Override @Nullable TransportBackChannel getTransportBackChannel(WSHTTPConnection con) {
624 return new Oneway(con);
625 }
627 protected @Override @NotNull
628 PropertySet getPropertySet(WSHTTPConnection con) {
629 return con;
630 }
632 protected @Override @NotNull WebServiceContextDelegate getWebServiceContextDelegate(WSHTTPConnection con) {
633 return con.getWebServiceContextDelegate();
634 }
635 }
637 static final class Oneway implements TransportBackChannel {
638 WSHTTPConnection con;
639 boolean closed;
641 Oneway(WSHTTPConnection con) {
642 this.con = con;
643 }
644 @Override
645 public void close() {
646 if (!closed) {
647 closed = true;
648 // close the response channel now
649 if (con.getStatus() == 0) {
650 // if the appliation didn't set the status code,
651 // set the default one.
652 con.setStatus(WSHTTPConnection.ONEWAY);
653 }
655 OutputStream output = null;
656 try {
657 output = con.getOutput();
658 } catch (IOException e) {
659 // no-op
660 }
662 if (dump || LOGGER.isLoggable(Level.FINER)) {
663 try {
664 ByteArrayBuffer buf = new ByteArrayBuffer();
665 dump(buf, "HTTP response " + con.getStatus(), con.getResponseHeaders());
666 } catch (Exception e) {
667 throw new WebServiceException(e.toString(), e);
668 }
669 }
671 if (output != null) {
672 try {
673 output.close(); // no payload
674 } catch (IOException e) {
675 throw new WebServiceException(e);
676 }
677 }
678 con.close();
679 }
680 }
681 }
683 final class HttpToolkit extends Adapter.Toolkit {
684 public void handle(WSHTTPConnection con) throws IOException {
685 try {
686 boolean invoke = false;
687 Packet packet;
688 try {
689 packet = decodePacket(con, codec);
690 invoke = true;
691 } catch(Exception e) {
692 packet = new Packet();
693 if (e instanceof ExceptionHasMessage) {
694 LOGGER.log(Level.SEVERE, e.getMessage(), e);
695 packet.setMessage(((ExceptionHasMessage)e).getFaultMessage());
696 } else if (e instanceof UnsupportedMediaException) {
697 LOGGER.log(Level.SEVERE, e.getMessage(), e);
698 con.setStatus(WSHTTPConnection.UNSUPPORTED_MEDIA);
699 } else {
700 LOGGER.log(Level.SEVERE, e.getMessage(), e);
701 con.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR);
702 }
703 }
704 if (invoke) {
705 try {
706 packet = head.process(packet, con.getWebServiceContextDelegate(),
707 packet.transportBackChannel);
708 } catch(Throwable e) {
709 LOGGER.log(Level.SEVERE, e.getMessage(), e);
710 if (!con.isClosed()) {
711 writeInternalServerError(con);
712 }
713 return;
714 }
715 }
716 encodePacket(packet, con, codec);
717 } finally {
718 if (!con.isClosed()) {
719 if (LOGGER.isLoggable(Level.FINE)) {
720 LOGGER.log(Level.FINE, "Closing HTTP Connection with status: {0}", con.getStatus());
721 }
722 con.close();
723 }
724 }
725 }
726 }
728 /**
729 * Returns true if the given query string is for metadata request.
730 *
731 * @param query
732 * String like "xsd=1" or "perhaps=some&unrelated=query".
733 * Can be null.
734 * @return true for metadata requests
735 * false for web service requests
736 */
737 private boolean isMetadataQuery(String query) {
738 // we intentionally return true even if documents don't exist,
739 // so that they get 404.
740 return query != null && (query.equals("WSDL") || query.startsWith("wsdl") || query.startsWith("xsd="));
741 }
743 /**
744 * Sends out the WSDL (and other referenced documents)
745 * in response to the GET requests to URLs like "?wsdl" or "?xsd=2".
746 *
747 * @param con
748 * The connection to which the data will be sent.
749 *
750 * @throws java.io.IOException when I/O errors happen
751 */
752 public void publishWSDL(@NotNull WSHTTPConnection con) throws IOException {
753 con.getInput().close();
755 SDDocument doc = wsdls.get(con.getQueryString());
756 if (doc == null) {
757 writeNotFoundErrorPage(con,"Invalid Request");
758 return;
759 }
761 con.setStatus(HttpURLConnection.HTTP_OK);
762 con.setContentTypeResponseHeader("text/xml;charset=utf-8");
764 OutputStream os = con.getProtocol().contains("1.1") ? con.getOutput() : new Http10OutputStream(con);
766 PortAddressResolver portAddressResolver = getPortAddressResolver(con.getBaseAddress());
767 DocumentAddressResolver resolver = getDocumentAddressResolver(portAddressResolver);
769 doc.writeTo(portAddressResolver, resolver, os);
770 os.close();
771 }
773 public PortAddressResolver getPortAddressResolver(String baseAddress) {
774 return owner.createPortAddressResolver(baseAddress, endpoint.getImplementationClass());
775 }
777 public DocumentAddressResolver getDocumentAddressResolver(
778 PortAddressResolver portAddressResolver) {
779 final String address = portAddressResolver.getAddressFor(endpoint.getServiceName(), endpoint.getPortName().getLocalPart());
780 assert address != null;
781 return new DocumentAddressResolver() {
782 @Override
783 public String getRelativeAddressFor(@NotNull SDDocument current, @NotNull SDDocument referenced) {
784 // the map on endpoint should account for all SDDocument
785 assert revWsdls.containsKey(referenced);
786 return address+'?'+ revWsdls.get(referenced);
787 }
788 };
789 }
791 /**
792 * HTTP/1.0 connections require Content-Length. So just buffer to find out
793 * the length.
794 */
795 private final static class Http10OutputStream extends ByteArrayBuffer {
796 private final WSHTTPConnection con;
798 Http10OutputStream(WSHTTPConnection con) {
799 this.con = con;
800 }
802 @Override
803 public void close() throws IOException {
804 super.close();
805 con.setContentLengthResponseHeader(size());
806 OutputStream os = con.getOutput();
807 writeTo(os);
808 os.close();
809 }
810 }
812 private void writeNotFoundErrorPage(WSHTTPConnection con, String message) throws IOException {
813 con.setStatus(HttpURLConnection.HTTP_NOT_FOUND);
814 con.setContentTypeResponseHeader("text/html; charset=utf-8");
816 PrintWriter out = new PrintWriter(new OutputStreamWriter(con.getOutput(),"UTF-8"));
817 out.println("<html>");
818 out.println("<head><title>");
819 out.println(WsservletMessages.SERVLET_HTML_TITLE());
820 out.println("</title></head>");
821 out.println("<body>");
822 out.println(WsservletMessages.SERVLET_HTML_NOT_FOUND(message));
823 out.println("</body>");
824 out.println("</html>");
825 out.close();
826 }
828 private void writeInternalServerError(WSHTTPConnection con) throws IOException {
829 con.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR);
830 con.getOutput().close(); // Sets the status code
831 }
833 private static final class DummyList extends HttpAdapterList<HttpAdapter> {
834 @Override
835 protected HttpAdapter createHttpAdapter(String name, String urlPattern, WSEndpoint<?> endpoint) {
836 return new HttpAdapter(endpoint,this,urlPattern);
837 }
838 }
840 private static void dump(ByteArrayBuffer buf, String caption, Map<String, List<String>> headers) throws IOException {
841 ByteArrayOutputStream baos = new ByteArrayOutputStream();
842 PrintWriter pw = new PrintWriter(baos, true);
843 pw.println("---["+caption +"]---");
844 if (headers != null) {
845 for (Entry<String, List<String>> header : headers.entrySet()) {
846 if (header.getValue().isEmpty()) {
847 // I don't think this is legal, but let's just dump it,
848 // as the point of the dump is to uncover problems.
849 pw.println(header.getValue());
850 } else {
851 for (String value : header.getValue()) {
852 pw.println(header.getKey() + ": " + value);
853 }
854 }
855 }
856 }
857 if (buf.size() > dump_threshold) {
858 byte[] b = buf.getRawData();
859 baos.write(b, 0, dump_threshold);
860 pw.println();
861 pw.println(WsservletMessages.MESSAGE_TOO_LONG(HttpAdapter.class.getName() + ".dumpTreshold"));
862 } else {
863 buf.writeTo(baos);
864 }
865 pw.println("--------------------");
867 String msg = baos.toString();
868 if (dump) {
869 System.out.println(msg);
870 }
871 if (LOGGER.isLoggable(Level.FINER)) {
872 LOGGER.log(Level.FINER, msg);
873 }
874 }
876 /*
877 * Generates the listing of all services.
878 */
879 private void writeWebServicesHtmlPage(WSHTTPConnection con) throws IOException {
880 if (!publishStatusPage) {
881 return;
882 }
884 // TODO: resurrect the ability to localize according to the current request.
886 con.getInput().close();
888 // standard browsable page
889 con.setStatus(WSHTTPConnection.OK);
890 con.setContentTypeResponseHeader("text/html; charset=utf-8");
892 PrintWriter out = new PrintWriter(new OutputStreamWriter(con.getOutput(),"UTF-8"));
893 out.println("<html>");
894 out.println("<head><title>");
895 // out.println("Web Services");
896 out.println(WsservletMessages.SERVLET_HTML_TITLE());
897 out.println("</title></head>");
898 out.println("<body>");
899 // out.println("<h1>Web Services</h1>");
900 out.println(WsservletMessages.SERVLET_HTML_TITLE_2());
902 // what endpoints do we have in this system?
903 Module module = getEndpoint().getContainer().getSPI(Module.class);
904 List<BoundEndpoint> endpoints = Collections.emptyList();
905 if(module!=null) {
906 endpoints = module.getBoundEndpoints();
907 }
909 if (endpoints.isEmpty()) {
910 // out.println("<p>No JAX-WS context information available.</p>");
911 out.println(WsservletMessages.SERVLET_HTML_NO_INFO_AVAILABLE());
912 } else {
913 out.println("<table width='100%' border='1'>");
914 out.println("<tr>");
915 out.println("<td>");
916 // out.println("Endpoint");
917 out.println(WsservletMessages.SERVLET_HTML_COLUMN_HEADER_PORT_NAME());
918 out.println("</td>");
920 out.println("<td>");
921 // out.println("Information");
922 out.println(WsservletMessages.SERVLET_HTML_COLUMN_HEADER_INFORMATION());
923 out.println("</td>");
924 out.println("</tr>");
926 for (BoundEndpoint a : endpoints) {
927 String endpointAddress = a.getAddress(con.getBaseAddress()).toString();
928 out.println("<tr>");
930 out.println("<td>");
931 out.println(WsservletMessages.SERVLET_HTML_ENDPOINT_TABLE(
932 a.getEndpoint().getServiceName(),
933 a.getEndpoint().getPortName()
934 ));
935 out.println("</td>");
937 out.println("<td>");
938 out.println(WsservletMessages.SERVLET_HTML_INFORMATION_TABLE(
939 endpointAddress,
940 a.getEndpoint().getImplementationClass().getName()
941 ));
942 out.println("</td>");
944 out.println("</tr>");
945 }
946 out.println("</table>");
947 }
948 out.println("</body>");
949 out.println("</html>");
950 out.close();
951 }
953 /**
954 * Dumps what goes across HTTP transport.
955 */
956 public static volatile boolean dump = false;
958 public static volatile int dump_threshold = 4096;
960 public static volatile boolean publishStatusPage = true;
962 public static synchronized void setPublishStatus(boolean publish) {
963 publishStatusPage = publish;
964 }
966 static {
967 try {
968 dump = Boolean.getBoolean(HttpAdapter.class.getName() + ".dump");
969 } catch (SecurityException se) {
970 if (LOGGER.isLoggable(Level.CONFIG)) {
971 LOGGER.log(Level.CONFIG, "Cannot read ''{0}'' property, using defaults.",
972 new Object[] {HttpAdapter.class.getName() + ".dump"});
973 }
974 }
975 try {
976 dump_threshold = Integer.getInteger(HttpAdapter.class.getName() + ".dumpTreshold", 4096);
977 } catch (SecurityException se) {
978 if (LOGGER.isLoggable(Level.CONFIG)) {
979 LOGGER.log(Level.CONFIG, "Cannot read ''{0}'' property, using defaults.",
980 new Object[] {HttpAdapter.class.getName() + ".dumpTreshold"});
981 }
982 }
983 try {
984 setPublishStatus(Boolean.getBoolean(HttpAdapter.class.getName() + ".publishStatusPage"));
985 } catch (SecurityException se) {
986 if (LOGGER.isLoggable(Level.CONFIG)) {
987 LOGGER.log(Level.CONFIG, "Cannot read ''{0}'' property, using defaults.",
988 new Object[] {HttpAdapter.class.getName() + ".publishStatusPage"});
989 }
990 }
991 }
993 public static void setDump(boolean dumpMessages) {
994 HttpAdapter.dump = dumpMessages;
995 }
996 }