1 /*
   2  * Copyright (c) 1997, 2017, 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.server;
  27 
  28 import com.sun.net.httpserver.HttpContext;
  29 import com.sun.net.httpserver.HttpServer;
  30 import com.sun.xml.internal.ws.server.ServerRtException;
  31 
  32 import java.net.InetSocketAddress;
  33 import java.net.URL;
  34 import java.util.HashMap;
  35 import java.util.HashSet;
  36 import java.util.Map;
  37 import java.util.Set;
  38 import java.util.concurrent.ExecutorService;
  39 import java.util.concurrent.Executors;
  40 import java.util.logging.Level;
  41 import java.util.logging.Logger;
  42 
  43 
  44 /**
  45  * Manages all the WebService HTTP servers created by JAXWS runtime.
  46  *
  47  * @author Jitendra Kotamraju
  48  */
  49 final class ServerMgr {
  50 
  51     private static final ServerMgr serverMgr = new ServerMgr();
  52     private static final Logger LOGGER =
  53         Logger.getLogger(
  54             com.sun.xml.internal.ws.util.Constants.LoggingDomain + ".server.http");
  55     private final Map<InetSocketAddress,ServerState> servers = new HashMap<>();
  56 
  57     private ServerMgr() {}
  58 
  59     /**
  60      * Gets the singleton instance.
  61      * @return manager instance
  62      */
  63     static ServerMgr getInstance() {
  64         return serverMgr;
  65     }
  66 
  67     /*
  68      * Creates a HttpContext at the given address. If there is already a server
  69      * it uses that server to create a context. Otherwise, it creates a new
  70      * HTTP server. This sever is added to servers Map.
  71      */
  72     /*package*/ HttpContext createContext(String address) {
  73         try {
  74             HttpServer server;
  75             ServerState state;
  76             URL url = new URL(address);
  77             int port = url.getPort();
  78             if (port == -1) {
  79                 port = url.getDefaultPort();
  80             }
  81             InetSocketAddress inetAddress = new InetSocketAddress(url.getHost(),
  82                 port);
  83             synchronized(servers) {
  84                 state = servers.get(inetAddress);
  85                 if (state == null) {
  86                     ServerState free = null;
  87                     for (ServerState ss : servers.values()) {
  88                         if (port == ss.getServer().getAddress().getPort()) {
  89                             free = ss;
  90                             break;
  91                         }
  92                     }
  93                     if (inetAddress.getAddress().isAnyLocalAddress() && free != null) {
  94                         state = free;
  95                     } else {
  96                         if (LOGGER.isLoggable(Level.FINE)) {
  97                             LOGGER.fine("Creating new HTTP Server at "+inetAddress);
  98                         }
  99                         // Creates server with default socket backlog
 100                         server = HttpServer.create(inetAddress, 0);
 101                         server.setExecutor(Executors.newCachedThreadPool());
 102                         String path = url.toURI().getPath();
 103                         if (LOGGER.isLoggable(Level.FINE)) {
 104                             LOGGER.fine("Creating HTTP Context at = "+path);
 105                         }
 106                         HttpContext context = server.createContext(path);
 107                         server.start();
 108 
 109                         // we have to get actual inetAddress from server, which can differ from the original in some cases.
 110                         // e.g. A port number of zero will let the system pick up an ephemeral port in a bind operation,
 111                         // or IP: 0.0.0.0 - which is used to monitor network traffic from any valid IP address
 112                         inetAddress = server.getAddress();
 113 
 114                         if (LOGGER.isLoggable(Level.FINE)) {
 115                             LOGGER.fine("HTTP server started = "+inetAddress);
 116                         }
 117                         state = new ServerState(server, path);
 118                         servers.put(inetAddress, state);
 119                         return context;
 120                     }
 121                 }
 122             }
 123             server = state.getServer();
 124 
 125             if (state.getPaths().contains(url.getPath())) {
 126               String err = "Context with URL path "+url.getPath()+ " already exists on the server "+server.getAddress();
 127               if (LOGGER.isLoggable(Level.FINE)) {
 128                 LOGGER.fine(err);
 129               }
 130               throw new IllegalArgumentException(err);
 131             }
 132 
 133             if (LOGGER.isLoggable(Level.FINE)) {
 134                 LOGGER.fine("Creating HTTP Context at = "+url.getPath());
 135             }
 136             HttpContext context = server.createContext(url.getPath());
 137             state.oneMoreContext(url.getPath());
 138             return context;
 139         } catch(Exception e) {
 140             throw new ServerRtException("server.rt.err",e );
 141         }
 142     }
 143 
 144     /*
 145      * Removes a context. If the server doesn't have anymore contexts, it
 146      * would stop the server and server is removed from servers Map.
 147      */
 148     /*package*/ void removeContext(HttpContext context) {
 149         InetSocketAddress inetAddress = context.getServer().getAddress();
 150         synchronized(servers) {
 151             ServerState state = servers.get(inetAddress);
 152             int instances = state.noOfContexts();
 153             if (instances < 2) {
 154                 ((ExecutorService)state.getServer().getExecutor()).shutdown();
 155                 state.getServer().stop(0);
 156                 servers.remove(inetAddress);
 157             } else {
 158                 state.getServer().removeContext(context);
 159                 state.oneLessContext(context.getPath());
 160             }
 161         }
 162     }
 163 
 164     private static final class ServerState {
 165         private final HttpServer server;
 166         private int instances;
 167         private final Set<String> paths = new HashSet<>();
 168 
 169         ServerState(HttpServer server, String path) {
 170             this.server = server;
 171             this.instances = 1;
 172             paths.add(path);
 173         }
 174 
 175         public HttpServer getServer() {
 176             return server;
 177         }
 178 
 179         public void oneMoreContext(String path) {
 180             ++instances;
 181             paths.add(path);
 182         }
 183 
 184         public void oneLessContext(String path) {
 185             --instances;
 186             paths.remove(path);
 187         }
 188 
 189         public int noOfContexts() {
 190             return instances;
 191         }
 192 
 193         public Set<String> getPaths() {
 194           return paths;
 195         }
 196     }
 197 }