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