com.wantedtech.common.xpresso.web.service.ExchangeImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xpresso Show documentation
Show all versions of xpresso Show documentation
The most pythonic way to code in Java
The newest version!
package com.wantedtech.common.xpresso.web.service;
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.logging.Logger;
import java.text.*;
class ExchangeImpl {
Headers reqHdrs, rspHdrs;
Request req;
String method;
boolean writefinished;
URI uri;
HttpConnection connection;
long reqContentLen;
long rspContentLen;
/* raw streams which access the socket directly */
InputStream ris;
OutputStream ros;
Thread thread;
/* close the underlying connection when this exchange finished */
boolean close;
boolean closed;
boolean http10 = false;
/* for formatting the Date: header */
private static final String pattern = "EEE, dd MMM yyyy HH:mm:ss zzz";
private static final TimeZone gmtTZ = TimeZone.getTimeZone("GMT");
private static final ThreadLocal dateFormat =
new ThreadLocal() {
@Override protected DateFormat initialValue() {
DateFormat df = new SimpleDateFormat(pattern, Locale.US);
df.setTimeZone(gmtTZ);
return df;
}
};
private static final String HEAD = "HEAD";
/* streams which take care of the HTTP protocol framing
* and are passed up to higher layers
*/
InputStream uis;
OutputStream uos;
LeftOverInputStream uis_orig; // uis may have be a user supplied wrapper
PlaceholderOutputStream uos_orig;
boolean sentHeaders; /* true after response headers sent */
Map attributes;
int rcode = -1;
HttpPrincipal principal;
ServerImpl server;
ExchangeImpl (
String m, URI u, Request req, long len, HttpConnection connection
) throws IOException {
this.req = req;
this.reqHdrs = req.headers();
this.rspHdrs = new Headers();
this.method = m;
this.uri = u;
this.connection = connection;
this.reqContentLen = len;
/* ros only used for headers, body written directly to stream */
this.ros = req.outputStream();
this.ris = req.inputStream();
server = getServerImpl();
server.startExchange();
}
public Headers getRequestHeaders () {
return new UnmodifiableHeaders (reqHdrs);
}
public Headers getResponseHeaders () {
return rspHdrs;
}
public URI getRequestURI () {
return uri;
}
public String getRequestMethod (){
return method;
}
public HttpContextImpl getHttpContext (){
return connection.getHttpContext();
}
private boolean isHeadRequest() {
return HEAD.equals(getRequestMethod());
}
public void close () {
if (closed) {
return;
}
closed = true;
/* close the underlying connection if,
* a) the streams not set up yet, no response can be sent, or
* b) if the wrapper output stream is not set up, or
* c) if the close of the input/outpu stream fails
*/
try {
if (uis_orig == null || uos == null) {
connection.close();
return;
}
if (!uos_orig.isWrapped()) {
connection.close();
return;
}
if (!uis_orig.isClosed()) {
uis_orig.close();
}
uos.close();
} catch (IOException e) {
connection.close();
}
}
public InputStream getRequestBody () {
if (uis != null) {
return uis;
}
if (reqContentLen == -1L) {
uis_orig = new ChunkedInputStream (this, ris);
uis = uis_orig;
} else {
uis_orig = new FixedLengthInputStream (this, ris, reqContentLen);
uis = uis_orig;
}
return uis;
}
LeftOverInputStream getOriginalInputStream () {
return uis_orig;
}
public int getResponseCode () {
return rcode;
}
public OutputStream getResponseBody () {
/* TODO. Change spec to remove restriction below. Filters
* cannot work with this restriction
*
* if (!sentHeaders) {
* throw new IllegalStateException ("headers not sent");
* }
*/
if (uos == null) {
uos_orig = new PlaceholderOutputStream (null);
uos = uos_orig;
}
return uos;
}
/* returns the place holder stream, which is the stream
* returned from the 1st call to getResponseBody()
* The "real" ouputstream is then placed inside this
*/
PlaceholderOutputStream getPlaceholderResponseBody () {
getResponseBody();
return uos_orig;
}
public void sendResponseHeaders (int rCode, long contentLen)
throws IOException
{
if (sentHeaders) {
throw new IOException ("headers already sent");
}
this.rcode = rCode;
String statusLine = "HTTP/1.1 "+rCode+Code.msg(rCode)+"\r\n";
OutputStream tmpout = new BufferedOutputStream (ros);
PlaceholderOutputStream o = getPlaceholderResponseBody();
tmpout.write (bytes(statusLine, 0), 0, statusLine.length());
boolean noContentToSend = false; // assume there is content
rspHdrs.set ("Date", dateFormat.get().format (new Date()));
/* check for response type that is not allowed to send a body */
if ((rCode>=100 && rCode <200) /* informational */
||(rCode == 204) /* no content */
||(rCode == 304)) /* not modified */
{
if (contentLen != -1) {
Logger logger = server.getLogger();
String msg = "sendResponseHeaders: rCode = "+ rCode
+ ": forcing contentLen = -1";
logger.warning (msg);
}
contentLen = -1;
}
if (isHeadRequest()) {
/* HEAD requests should not set a content length by passing it
* through this API, but should instead manually set the required
* headers.*/
if (contentLen >= 0) {
final Logger logger = server.getLogger();
String msg =
"sendResponseHeaders: being invoked with a content length for a HEAD request";
logger.warning (msg);
}
noContentToSend = true;
contentLen = 0;
} else { /* not a HEAD request */
if (contentLen == 0) {
if (http10) {
o.setWrappedStream (new UndefLengthOutputStream (this, ros));
close = true;
} else {
rspHdrs.set ("Transfer-encoding", "chunked");
o.setWrappedStream (new ChunkedOutputStream (this, ros));
}
} else {
if (contentLen == -1) {
noContentToSend = true;
contentLen = 0;
}
rspHdrs.set("Content-length", Long.toString(contentLen));
o.setWrappedStream (new FixedLengthOutputStream (this, ros, contentLen));
}
}
write (rspHdrs, tmpout);
this.rspContentLen = contentLen;
tmpout.flush() ;
tmpout = null;
sentHeaders = true;
if (noContentToSend) {
WriteFinishedEvent e = new WriteFinishedEvent (this);
server.addEvent (e);
closed = true;
}
server.logReply (rCode, req.requestLine(), null);
}
void write (Headers map, OutputStream os) throws IOException {
Set>> entries = map.entrySet();
for (Map.Entry> entry : entries) {
String key = entry.getKey();
byte[] buf;
List values = entry.getValue();
for (String val : values) {
int i = key.length();
buf = bytes (key, 2);
buf[i++] = ':';
buf[i++] = ' ';
os.write (buf, 0, i);
buf = bytes (val, 2);
i = val.length();
buf[i++] = '\r';
buf[i++] = '\n';
os.write (buf, 0, i);
}
}
os.write ('\r');
os.write ('\n');
}
private byte[] rspbuf = new byte [128]; // used by bytes()
/**
* convert string to byte[], using rspbuf
* Make sure that at least "extra" bytes are free at end
* of rspbuf. Reallocate rspbuf if not big enough.
* caller must check return value to see if rspbuf moved
*/
private byte[] bytes (String s, int extra) {
int slen = s.length();
if (slen+extra > rspbuf.length) {
int diff = slen + extra - rspbuf.length;
rspbuf = new byte [2* (rspbuf.length + diff)];
}
char c[] = s.toCharArray();
for (int i=0; i