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 */ 25 26 package com.sun.xml.internal.ws.transport.http; 27 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; 43 44 import javax.xml.ws.Binding; 45 import javax.xml.ws.WebServiceException; 46 import javax.xml.ws.http.HTTPBinding; 47 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; 78 79 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> { 91 92 private static final Logger LOGGER = Logger.getLogger(HttpAdapter.class.getName()); 93 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; 102 103 /** 104 * Reverse map of {@link #wsdls}. Read-only. 105 */ 106 private Map<SDDocument,String> revWsdls; 107 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; 114 115 public final HttpAdapterList<? extends HttpAdapter> owner; 116 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; 121 122 protected boolean stickyCookie; 123 124 protected boolean disableJreplicaCookie = false; 125 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 } 139 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 } 150 151 protected HttpAdapter(WSEndpoint endpoint, 152 HttpAdapterList<? extends HttpAdapter> owner, 153 String urlPattern) { 154 super(endpoint); 155 this.owner = owner; 156 this.urlPattern = urlPattern; 157 158 initWSDLMap(endpoint.getServiceDefinition()); 159 } 160 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 } 169 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 } 193 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 } 205 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 } 214 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 } 226 227 @Override 228 protected HttpToolkit createToolkit() { 229 return new HttpToolkit(); 230 } 231 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 } 254 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 } 265 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 } 275 276 if (isMetadataQuery(connection.getQueryString())) { 277 // Sends published WSDL and schema documents as the default action. 278 publishWSDL(connection); 279 return true; 280 } 281 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 } 307 308 return false; 309 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 } 344 345 protected void addSatellites(Packet packet) { 346 } 347 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 } 371 372 protected NonAnonymousResponseProcessor getNonAnonymousResponseProcessor() { 373 return NonAnonymousResponseProcessor.getDefault(); 374 } 375 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 } 384 385 private boolean isClientErrorStatus(int connStatus) 386 { 387 return (connStatus == HttpURLConnection.HTTP_FORBIDDEN); // add more for future. 388 } 389 390 private boolean isNonAnonymousUri(EndpointAddress addr){ 391 return (addr != null) && !addr.toString().equals(AddressingVersion.W3C.anonymousUri) && 392 !addr.toString().equals(AddressingVersion.MEMBER.anonymousUri); 393 } 394 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 } 411 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 } 449 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 } 463 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 { 478 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 } 491 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 } 511 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 } 519 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 } 534 535 public void invokeAsync(final WSHTTPConnection con) throws IOException { 536 invokeAsync(con, NO_OP_COMPLETION_CALLBACK); 537 } 538 539 public void invokeAsync(final WSHTTPConnection con, final CompletionCallback callback) throws IOException { 540 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; 548 549 try { 550 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 } 571 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(); 585 586 } 587 } 588 },null); 589 590 } 591 592 public static final CompletionCallback NO_OP_COMPLETION_CALLBACK = new CompletionCallback() { 593 594 @Override 595 public void onCompletion() { 596 //NO-OP 597 } 598 }; 599 600 public interface CompletionCallback{ 601 void onCompletion(); 602 } 603 604 final class AsyncTransport extends AbstractServerAsyncTransport<WSHTTPConnection> { 605 606 public AsyncTransport() { 607 super(endpoint); 608 } 609 610 public void handleAsync(WSHTTPConnection con) throws IOException { 611 super.handle(con); 612 } 613 614 @Override 615 protected void encodePacket(WSHTTPConnection con, @NotNull Packet packet, @NotNull Codec codec) throws IOException { 616 HttpAdapter.this.encodePacket(packet, con, codec); 617 } 618 619 protected @Override @Nullable String getAcceptableMimeTypes(WSHTTPConnection con) { 620 return null; 621 } 622 623 protected @Override @Nullable TransportBackChannel getTransportBackChannel(WSHTTPConnection con) { 624 return new Oneway(con); 625 } 626 627 protected @Override @NotNull 628 PropertySet getPropertySet(WSHTTPConnection con) { 629 return con; 630 } 631 632 protected @Override @NotNull WebServiceContextDelegate getWebServiceContextDelegate(WSHTTPConnection con) { 633 return con.getWebServiceContextDelegate(); 634 } 635 } 636 637 static final class Oneway implements TransportBackChannel { 638 WSHTTPConnection con; 639 boolean closed; 640 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 } 654 655 OutputStream output = null; 656 try { 657 output = con.getOutput(); 658 } catch (IOException e) { 659 // no-op 660 } 661 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 } 670 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 } 682 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 } 727 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 } 742 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(); 754 755 SDDocument doc = wsdls.get(con.getQueryString()); 756 if (doc == null) { 757 writeNotFoundErrorPage(con,"Invalid Request"); 758 return; 759 } 760 761 con.setStatus(HttpURLConnection.HTTP_OK); 762 con.setContentTypeResponseHeader("text/xml;charset=utf-8"); 763 764 OutputStream os = con.getProtocol().contains("1.1") ? con.getOutput() : new Http10OutputStream(con); 765 766 PortAddressResolver portAddressResolver = getPortAddressResolver(con.getBaseAddress()); 767 DocumentAddressResolver resolver = getDocumentAddressResolver(portAddressResolver); 768 769 doc.writeTo(portAddressResolver, resolver, os); 770 os.close(); 771 } 772 773 public PortAddressResolver getPortAddressResolver(String baseAddress) { 774 return owner.createPortAddressResolver(baseAddress, endpoint.getImplementationClass()); 775 } 776 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 } 790 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; 797 798 Http10OutputStream(WSHTTPConnection con) { 799 this.con = con; 800 } 801 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 } 811 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"); 815 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 } 827 828 private void writeInternalServerError(WSHTTPConnection con) throws IOException { 829 con.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR); 830 con.getOutput().close(); // Sets the status code 831 } 832 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 } 839 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("--------------------"); 866 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 } 875 876 /* 877 * Generates the listing of all services. 878 */ 879 private void writeWebServicesHtmlPage(WSHTTPConnection con) throws IOException { 880 if (!publishStatusPage) { 881 return; 882 } 883 884 // TODO: resurrect the ability to localize according to the current request. 885 886 con.getInput().close(); 887 888 // standard browsable page 889 con.setStatus(WSHTTPConnection.OK); 890 con.setContentTypeResponseHeader("text/html; charset=utf-8"); 891 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()); 901 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 } 908 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>"); 919 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>"); 925 926 for (BoundEndpoint a : endpoints) { 927 String endpointAddress = a.getAddress(con.getBaseAddress()).toString(); 928 out.println("<tr>"); 929 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>"); 936 937 out.println("<td>"); 938 out.println(WsservletMessages.SERVLET_HTML_INFORMATION_TABLE( 939 endpointAddress, 940 a.getEndpoint().getImplementationClass().getName() 941 )); 942 out.println("</td>"); 943 944 out.println("</tr>"); 945 } 946 out.println("</table>"); 947 } 948 out.println("</body>"); 949 out.println("</html>"); 950 out.close(); 951 } 952 953 /** 954 * Dumps what goes across HTTP transport. 955 */ 956 public static volatile boolean dump = false; 957 958 public static volatile int dump_threshold = 4096; 959 960 public static volatile boolean publishStatusPage = true; 961 962 public static synchronized void setPublishStatus(boolean publish) { 963 publishStatusPage = publish; 964 } 965 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 } 992 993 public static void setDump(boolean dumpMessages) { 994 HttpAdapter.dump = dumpMessages; 995 } 996 }