1 /* 2 * Copyright (c) 1996, 2012, 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 package sun.rmi.transport.proxy; 26 27 import java.io.*; 28 import java.net.*; 29 import java.security.PrivilegedAction; 30 31 import sun.rmi.runtime.Log; 32 33 /** 34 * The HttpSendSocket class extends the java.net.Socket class 35 * by enclosing the data output stream in, then extracting the input 36 * stream from, an HTTP protocol transmission. 37 * 38 * NOTES: 39 * 40 * Since the length of the output request must be known before the 41 * HTTP header can be completed, all of the output is buffered by 42 * an HttpOutputStream object until either an attempt is made to 43 * read from this socket, or the socket is explicitly closed. 44 * 45 * On the first read attempt to read from this socket, the buffered 46 * output is sent to the destination as the body of an HTTP POST 47 * request. All reads will then acquire data from the body of 48 * the response. A subsequent attempt to write to this socket will 49 * throw an IOException. 50 */ 51 class HttpSendSocket extends Socket implements RMISocketInfo { 52 53 /** the host to connect to */ 54 protected String host; 55 56 /** the port to connect to */ 57 protected int port; 58 59 /** the URL to forward through */ 60 protected URL url; 61 62 /** the object managing this connection through the URL */ 63 protected URLConnection conn = null; 64 65 /** internal input stream for this socket */ 66 protected InputStream in = null; 67 68 /** internal output stream for this socket */ 69 protected OutputStream out = null; 70 71 /** the notifying input stream returned to users */ 72 protected HttpSendInputStream inNotifier; 73 74 /** the notifying output stream returned to users */ 75 protected HttpSendOutputStream outNotifier; 76 77 /** 78 * Line separator string. This is the value of the line.separator 79 * property at the moment that the socket was created. 80 */ 81 private String lineSeparator = 82 java.security.AccessController.doPrivileged( 83 (PrivilegedAction<String>) () -> System.getProperty("line.separator")); 84 85 /** 86 * Create a stream socket and connect it to the specified port on 87 * the specified host. 88 * @param host the host 89 * @param port the port 90 */ 91 public HttpSendSocket(String host, int port, URL url) throws IOException 92 { 93 super((SocketImpl)null); // no underlying SocketImpl for this object 94 95 if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.VERBOSE)) { 96 RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE, 97 "host = " + host + ", port = " + port + ", url = " + url); 98 } 99 100 this.host = host; 101 this.port = port; 102 this.url = url; 103 104 inNotifier = new HttpSendInputStream(null, this); 105 outNotifier = new HttpSendOutputStream(writeNotify(), this); 106 } 107 108 /** 109 * Create a stream socket and connect it to the specified port on 110 * the specified host. 111 * @param host the host 112 * @param port the port 113 */ 114 public HttpSendSocket(String host, int port) throws IOException 115 { 116 this(host, port, new URL("http", host, port, "/")); 117 } 118 119 /** 120 * Create a stream socket and connect it to the specified address on 121 * the specified port. 122 * @param address the address 123 * @param port the port 124 */ 125 public HttpSendSocket(InetAddress address, int port) throws IOException 126 { 127 this(address.getHostName(), port); 128 } 129 130 /** 131 * Indicate that this socket is not reusable. 132 */ 133 public boolean isReusable() 134 { 135 return false; 136 } 137 138 /** 139 * Create a new socket connection to host (or proxy), and prepare to 140 * send HTTP transmission. 141 */ 142 public synchronized OutputStream writeNotify() throws IOException 143 { 144 if (conn != null) { 145 throw new IOException("attempt to write on HttpSendSocket after " + 146 "request has been sent"); 147 } 148 149 conn = url.openConnection(); 150 conn.setDoOutput(true); 151 conn.setUseCaches(false); 152 conn.setRequestProperty("Content-type", "application/octet-stream"); 153 154 inNotifier.deactivate(); 155 in = null; 156 157 return out = conn.getOutputStream(); 158 } 159 160 /** 161 * Send HTTP output transmission and prepare to receive response. 162 */ 163 public synchronized InputStream readNotify() throws IOException 164 { 165 RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE, 166 "sending request and activating input stream"); 167 168 outNotifier.deactivate(); 169 out.close(); 170 out = null; 171 172 try { 173 in = conn.getInputStream(); 174 } catch (IOException e) { 175 RMIMasterSocketFactory.proxyLog.log(Log.BRIEF, 176 "failed to get input stream, exception: ", e); 177 178 throw new IOException("HTTP request failed"); 179 } 180 181 /* 182 * If an HTTP error response is returned, sometimes an IOException 183 * is thrown, which is handled above, and other times it isn't, and 184 * the error response body will be available for reading. 185 * As a safety net to catch any such unexpected HTTP behavior, we 186 * verify that the content type of the response is what the 187 * HttpOutputStream generates: "application/octet-stream". 188 * (Servers' error responses will generally be "text/html".) 189 * Any error response body is printed to the log. 190 */ 191 String contentType = conn.getContentType(); 192 if (contentType == null || 193 !conn.getContentType().equals("application/octet-stream")) 194 { 195 if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.BRIEF)) { 196 String message; 197 if (contentType == null) { 198 message = "missing content type in response" + 199 lineSeparator; 200 } else { 201 message = "invalid content type in response: " + 202 contentType + lineSeparator; 203 } 204 205 message += "HttpSendSocket.readNotify: response body: "; 206 try { 207 BufferedReader din = new BufferedReader(new InputStreamReader(in)); 208 String line; 209 while ((line = din.readLine()) != null) 210 message += line + lineSeparator; 211 } catch (IOException e) { 212 } 213 RMIMasterSocketFactory.proxyLog.log(Log.BRIEF, message); 214 } 215 216 throw new IOException("HTTP request failed"); 217 } 218 219 return in; 220 } 221 222 /** 223 * Get the address to which the socket is connected. 224 */ 225 public InetAddress getInetAddress() 226 { 227 try { 228 return InetAddress.getByName(host); 229 } catch (UnknownHostException e) { 230 return null; // null if couldn't resolve destination host 231 } 232 } 233 234 /** 235 * Get the local address to which the socket is bound. 236 */ 237 public InetAddress getLocalAddress() 238 { 239 try { 240 return InetAddress.getLocalHost(); 241 } catch (UnknownHostException e) { 242 return null; // null if couldn't determine local host 243 } 244 } 245 246 /** 247 * Get the remote port to which the socket is connected. 248 */ 249 public int getPort() 250 { 251 return port; 252 } 253 254 /** 255 * Get the local port to which the socket is connected. 256 */ 257 public int getLocalPort() 258 { 259 return -1; // request not applicable to this socket type 260 } 261 262 /** 263 * Get an InputStream for this socket. 264 */ 265 public InputStream getInputStream() throws IOException 266 { 267 return inNotifier; 268 } 269 270 /** 271 * Get an OutputStream for this socket. 272 */ 273 public OutputStream getOutputStream() throws IOException 274 { 275 return outNotifier; 276 } 277 278 /** 279 * Enable/disable TCP_NODELAY. 280 * This operation has no effect for an HttpSendSocket. 281 */ 282 public void setTcpNoDelay(boolean on) throws SocketException 283 { 284 } 285 286 /** 287 * Retrieve whether TCP_NODELAY is enabled. 288 */ 289 public boolean getTcpNoDelay() throws SocketException 290 { 291 return false; // imply option is disabled 292 } 293 294 /** 295 * Enable/disable SO_LINGER with the specified linger time. 296 * This operation has no effect for an HttpSendSocket. 297 */ 298 public void setSoLinger(boolean on, int val) throws SocketException 299 { 300 } 301 302 /** 303 * Retrive setting for SO_LINGER. 304 */ 305 public int getSoLinger() throws SocketException 306 { 307 return -1; // imply option is disabled 308 } 309 310 /** 311 * Enable/disable SO_TIMEOUT with the specified timeout 312 * This operation has no effect for an HttpSendSocket. 313 */ 314 public synchronized void setSoTimeout(int timeout) throws SocketException 315 { 316 } 317 318 /** 319 * Retrive setting for SO_TIMEOUT. 320 */ 321 public synchronized int getSoTimeout() throws SocketException 322 { 323 return 0; // imply option is disabled 324 } 325 326 /** 327 * Close the socket. 328 */ 329 public synchronized void close() throws IOException 330 { 331 if (out != null) // push out transmission if not done 332 out.close(); 333 } 334 335 /** 336 * Return string representation of this pseudo-socket. 337 */ 338 public String toString() 339 { 340 return "HttpSendSocket[host=" + host + 341 ",port=" + port + 342 ",url=" + url + "]"; 343 } 344 }