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