diff --git a/src/org/push/impl/http/HttpConnection.java b/src/org/push/impl/http/HttpConnection.java new file mode 100644 index 0000000..2ccf4eb --- /dev/null +++ b/src/org/push/impl/http/HttpConnection.java @@ -0,0 +1,102 @@ +package org.push.impl.http; + +import org.push.core.LogicalConnection; +import org.push.core.ServerImpl; +import org.push.core.Common.Login; +import org.push.core.Common.LoginData; +import org.push.protocol.IncomingPacket; + +public abstract class HttpConnection extends LogicalConnection { + + private static final String HTTP_VERSION_1_0 = "HTTP/1.0"; + private static final String HTTP_VERSION_1_1 = "HTTP/1.1"; + + public HttpConnection(ServerImpl serverImpl) { + super(serverImpl); + } + + @Override + protected void recycle() { + ; + } + + @Override + public void handleRequest(IncomingPacket request) { + if (!(request instanceof HttpRequest)) { + return; + } + + HttpRequest httpReq = (HttpRequest)request; + HttpResponse httpResp = new HttpResponse(); + + process(httpReq, httpResp); + + // send back response + pushPacket(httpResp); + } + + @Override + protected Login processLogin(LoginData loginData) { + return Login.AcceptClientAndRouteRequest; + } + + protected void process(HttpRequest request, HttpResponse response) { + if (!checkVersion(request, response)) { + return; + } + + // Version + response.setVersion(request.getVersion()); + + String strMethod = request.getMethod(); + // No Method + if (strMethod == null || "".equals(strMethod)) { + response.setStatus(HttpStatus.BadRequest); + return; + } + + // Parse the parameters + if (HttpRequest.METHOD_GET.equalsIgnoreCase(strMethod)) { + parseGetParameters(request); + } else if (HttpRequest.METHOD_POST.equalsIgnoreCase(strMethod)) { + parsePostParameters(request); + } else { + response.setStatus(HttpStatus.MethodNotAllowed); + return; + } + } + + /** + * Check the version in the request, and if the version is + * invalid, a corresponding response will be given. + * @param request + * @param response + * @return true if the version is valid + */ + private boolean checkVersion(HttpRequest request, HttpResponse response) { + String strVersion = request.getVersion(); + if (strVersion == null || "".equals(strVersion)) { + response.setStatus(HttpStatus.BadRequest); + response.setVersion(HTTP_VERSION_1_1); + return false; + } + + if (!HTTP_VERSION_1_1.equals(strVersion) && + !HTTP_VERSION_1_0.equals(strVersion)) { + response.setStatus(HttpStatus.HTTPVersionNotSupported); + response.setVersion(HTTP_VERSION_1_1); + return false; + } + + return true; + } + + private void parseGetParameters(HttpRequest request) { + + } + + private void parsePostParameters(HttpRequest request) { + + } + +} diff --git a/src/org/push/impl/http/HttpDecodingFailureException.java b/src/org/push/impl/http/HttpDecodingFailureException.java new file mode 100644 index 0000000..f9a8f02 --- /dev/null +++ b/src/org/push/impl/http/HttpDecodingFailureException.java @@ -0,0 +1,25 @@ +package org.push.impl.http; + +public class HttpDecodingFailureException extends Exception { + + /** + * + */ + private static final long serialVersionUID = -3146357111549884066L; + + public HttpDecodingFailureException() { + } + + public HttpDecodingFailureException(String message) { + super(message); + } + + public HttpDecodingFailureException(Throwable cause) { + super(cause); + } + + public HttpDecodingFailureException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/org/push/impl/http/HttpEntity.java b/src/org/push/impl/http/HttpEntity.java new file mode 100644 index 0000000..ae54f44 --- /dev/null +++ b/src/org/push/impl/http/HttpEntity.java @@ -0,0 +1,12 @@ +package org.push.impl.http; + +public class HttpEntity { + + private byte[] bytes; + public HttpEntity() { + } + + public byte[] getBytes() { return this.bytes; } + + public void setBytes(byte[] bytes) { this.bytes = bytes; } +} diff --git a/src/org/push/impl/http/HttpMessageFactory.java b/src/org/push/impl/http/HttpMessageFactory.java new file mode 100644 index 0000000..206ac2a --- /dev/null +++ b/src/org/push/impl/http/HttpMessageFactory.java @@ -0,0 +1,208 @@ +package org.push.impl.http; + +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; + +import org.push.protocol.Buffer; +import org.push.protocol.DeserializeData; +import org.push.protocol.IncomingPacket; +import org.push.protocol.MessageFactory; +import org.push.protocol.OutgoingPacket; +import org.push.protocol.ErrorCodes.DeserializeResult; +import org.push.protocol.ErrorCodes.SerializeResult; + +public class HttpMessageFactory extends MessageFactory { + + public HttpMessageFactory() { } + + @Override + public SerializeResult serializeMessage(OutgoingPacket outgoingPacket, + Buffer buffer) { + if (!(outgoingPacket instanceof HttpResponse)) { + return SerializeResult.Failure; + } + + String strLineSep = "\r\n"; + StringBuilder sb = new StringBuilder(); + + HttpResponse httpResp = (HttpResponse)outgoingPacket; + + // First Line + sb.append(httpResp.getVersion()).append(" "); // http version + sb.append(httpResp.getStatus().code()).append(" "); // status code + sb.append(httpResp.getStatus().message()).append(" "); // message + sb.append(strLineSep); // next line + + // Headers + Map headers = httpResp.getHeaders(); + for (Map.Entry entry : headers.entrySet()) { + sb.append(entry.getKey()).append(": "); + sb.append(entry.getValue()).append(strLineSep); + } + sb.append(strLineSep); // end of headers + + String str = sb.toString(); + byte[] bytes = null; + + try { + bytes = str.getBytes("US-ASCII"); + } catch (UnsupportedEncodingException e) { + return SerializeResult.Failure; + } + + if (!buffer.append(bytes)) { + return SerializeResult.InsufficientBufferSpace; + } + + // Entity + byte[] entity = httpResp.getEntity().getBytes(); + if (entity != null && !buffer.append(entity)) { + return SerializeResult.InsufficientBufferSpace; + } + + return SerializeResult.Success; + } + + @Override + public DeserializeResult deserializeMessage(Buffer contentBytes, + DeserializeData deserializeData) { + byte[] content = contentBytes.getBuffer(); + int startIndex = 0; + StringBuilder sbLine = new StringBuilder(); + + try { + startIndex = nextLine(content, startIndex, sbLine); + } catch (HttpDecodingFailureException e) { + return DeserializeResult.Failure; + } + + // First Line + String firstLine = sbLine.toString(); + if (firstLine.length() == 0) { + return DeserializeResult.DiscardContent; + } + + // ; + String[] strs = firstLine.split(" "); + if (strs.length != 3) { + return DeserializeResult.Failure; + } + + String method = strs[0].trim(); + String url = strs[1].trim(); + String version = strs[2].trim(); + + // Headers + String header = null; + Map headers = new HashMap(); + int end = 0; + do { + sbLine.delete(0, sbLine.length()); + try { + end = nextLine(content, startIndex, sbLine); + } catch (HttpDecodingFailureException e) { + return DeserializeResult.Failure; + } + + // Line must be end with '\n' + if (content[end - 1] != 13) { + return DeserializeResult.Failure; + } + + header = sbLine.toString(); + + strs = header.split(":"); + if (strs.length != 2) { + return DeserializeResult.Failure; + } + + // save head name and value + headers.put(strs[0].trim(), strs[1].trim()); + + startIndex = end; + + } while (header != null && !"".equals(header)); + + // Entity + int entityLength = content.length - end; + byte[] entity = null; + if (entityLength > 0) { + entity = new byte[entityLength]; + } + + System.arraycopy(content, end, entity, 0, entityLength); + + HttpRequest httpReq = new HttpRequest(); + + httpReq.setMethod(method); + httpReq.setUrl(url); + httpReq.setVersion(version); + httpReq.setHeaders(headers); + + HttpEntity entityObject = new HttpEntity(); + entityObject.setBytes(entity); + httpReq.setEntity(entityObject); + + deserializeData.setMessage(httpReq); + + return DeserializeResult.Success; + } + + private static int nextLine(byte[] bytes, int startIndex, + StringBuilder sb) throws HttpDecodingFailureException { + if (bytes == null) { + return 0; + } + + int length = bytes.length; + if (length == 0) { + return 0; + } + + int start = startIndex; + // Start from 0 + if (start < 0) { + start = 0; + } + + byte b = 0; + int i = start; + for (; i < length; i ++) { + b = bytes[i]; + if (b == 10) { + int end = i; + if (bytes[i - 1] == 13) { + end --; + } + try { + sb.append(new String(bytes, start, end - start, + "US-ASCII")); + } catch (UnsupportedEncodingException e) { + throw new HttpDecodingFailureException(e); + } + + return end + 1; + } + } + + if (i > start) { + try { + sb.append(new String(bytes, start, i - start, + "US-ASCII")); + } catch (UnsupportedEncodingException e) { + throw new HttpDecodingFailureException(e); + } + } + + return i; + + } + + @Override + public void disposeIncomingPacket(IncomingPacket packet) { } + + @Override + public void disposeOutgoingPacket(OutgoingPacket packet) { } + +} diff --git a/src/org/push/impl/http/HttpProtocol.java b/src/org/push/impl/http/HttpProtocol.java new file mode 100644 index 0000000..bbf7387 --- /dev/null +++ b/src/org/push/impl/http/HttpProtocol.java @@ -0,0 +1,36 @@ +package org.push.impl.http; + +import org.push.protocol.Buffer; +import org.push.protocol.Protocol; +import org.push.protocol.ProtocolContext; +import org.push.protocol.ErrorCodes.DecodeResult; +import org.push.protocol.ErrorCodes.EncodeResult; + +public class HttpProtocol extends Protocol { + + public HttpProtocol() { + } + + @Override + public void startSession(ProtocolContext context, Buffer outgoingBytes) { + + } + + @Override + public boolean readData(ProtocolContext context, Buffer incomingBytes) { + return false; + } + + @Override + public DecodeResult tryDecode(ProtocolContext context, + Buffer outputBuffer) { + return null; + } + + @Override + public EncodeResult encodeContent(ProtocolContext context, + Buffer inputBuffer, Buffer outputBuffer) { + return null; + } + +} diff --git a/src/org/push/impl/http/HttpRequest.java b/src/org/push/impl/http/HttpRequest.java new file mode 100644 index 0000000..1eddf38 --- /dev/null +++ b/src/org/push/impl/http/HttpRequest.java @@ -0,0 +1,53 @@ +package org.push.impl.http; + +import java.util.HashMap; +import java.util.Map; + +import org.push.protocol.IncomingPacket; + +public class HttpRequest implements IncomingPacket { + + public static final String METHOD_GET = "GET"; + public static final String METHOD_POST = "POST"; + + private String method; + private String url; + private String version; + + private Map headers = new HashMap(); + private Map params = new HashMap(); + + private HttpEntity entity; + + HttpRequest() { + ; + } + + public String getMethod() { return this.method; } + + public void setMethod(String method) { this.method = method; } + + public String getUrl() { return this.url; } + + public void setUrl(String url) { this.url = url; } + + public String getVersion() { return this.version; } + + public void setVersion(String version) { this.version = version; } + + public Map getHeaders() { return this.headers; } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public Map getParameters() { return this.params; } + + void setParameters(Map params) { + this.params = params; + } + + public HttpEntity getEntity() { return this.entity; } + + public void setEntity(HttpEntity entity) { this.entity = entity; } +} diff --git a/src/org/push/impl/http/HttpResponse.java b/src/org/push/impl/http/HttpResponse.java new file mode 100644 index 0000000..5e962d1 --- /dev/null +++ b/src/org/push/impl/http/HttpResponse.java @@ -0,0 +1,36 @@ +package org.push.impl.http; + +import java.util.HashMap; +import java.util.Map; + +import org.push.protocol.OutgoingPacket; + +public class HttpResponse implements OutgoingPacket { + + private HttpStatus status; + private String version; + + private Map headers = new HashMap(); + + private HttpEntity entity; + + HttpResponse() { } + + public HttpStatus getStatus() { return this.status; } + + public void setStatus(HttpStatus status) { this.status = status; } + + public String getVersion() { return this.version; } + + public void setVersion(String version) { this.version = version; } + + public Map getHeaders() { return this.headers; } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public HttpEntity getEntity() { return this.entity; } + + public void setEntity(HttpEntity entity) { this.entity = entity; } +} diff --git a/src/org/push/impl/http/HttpStatus.java b/src/org/push/impl/http/HttpStatus.java new file mode 100644 index 0000000..642d10e --- /dev/null +++ b/src/org/push/impl/http/HttpStatus.java @@ -0,0 +1,61 @@ +package org.push.impl.http; + +public enum HttpStatus { + + Continue(100, "Continue"), + SwitchingProtocols(101, "Switching Protocols"), + // + OK(200, "OK"), + Created(201, "Created"), + Accepted(202, "Accepted"), + NonAuthoritativeInformation(203, "Non-Authoritative Information"), + NoContent(204, "No Content"), + ResetContent(205, "Reset Content"), + PartialContent(206, "Partial Content"), + // + MulipleChoices(300, "Muliple Choices"), + MovedPermanently(301, "Moved Permanently"), + Found(302, "Found"), + SeeOther(303, "See Other"), + NotModified(304, "Not Modified"), + UseProxy(305, "Use Proxy"), + TemporaryRedirect(307, "Temporary Redirect"), + // + BadRequest(400, "Bad Request"), + Unauthorized(401, "Unauthorized"), + PaymentRequired(402, "Payment Required"), + Forbidden(403, "Forbidden"), + NotFound(404, "Not Found"), + MethodNotAllowed(405, "Method Not Allowed"), + NotAcceptable(406, "Not Acceptable"), + ProxyAuthenticationRequired(407, "Proxy Authentication Required"), + RequestTimeout(408, "Request Timeout"), + Confilict(409, "Confilict"), + Gone(410, "Gone"), + LengthRequired(411, "Length Required"), + PreconditionFailed(412, "Precondition Failed"), + RequestEntityTooLarge(413, "Request Entity Too Large"), + RequestURITooLong(414, "Request URI Too Long"), + UnsupportedMediaType(415, "Unsupported Media Type"), + RequestedRangeNotSatisfiable(416, "Requested Range Not Satisfiable"), + ExpectationFailed(417, "Expectation Failed"), + // + InternalServerError(500, "Internal Server Error"), + NotImplemented(501, "Not Implemented"), + BadGateway(502, "Bad Gateway"), + ServiceUnavailable(503, "Service Unavailable"), + GatewayTimeout(504, "Gateway Timeout"), + HTTPVersionNotSupported(505, "HTTP Version Not Supported"); + + private int code; + private String msg; + + private HttpStatus(int code, String msg) { + this.code = code; + this.msg = msg; + } + + public int code() { return this.code; } + + public String message() { return this.msg; } +} diff --git a/src/org/push/protocol/ProtocolManager.java b/src/org/push/protocol/ProtocolManager.java index 4db2ad6..3be3cba 100644 --- a/src/org/push/protocol/ProtocolManager.java +++ b/src/org/push/protocol/ProtocolManager.java @@ -142,7 +142,7 @@ public NetworkDeserializeResult tryDeserializeIncomingPacket( } else { tmpOutputBuffer.release(); return NetworkDeserializeResult.WantMoreData; - } + } } else if (decodeResult == DecodeResult.NoContent) { continue; } else if (decodeResult == DecodeResult.ProtocolBytes) {