package com.googlecode.mycontainer.commons.http; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.Closeable; import java.io.IOException; import java.io.OutputStream; import java.io.PushbackInputStream; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; import javax.net.SocketFactory; import com.googlecode.mycontainer.commons.io.IOUtil; import com.googlecode.mycontainer.commons.lang.StringUtil; import com.googlecode.mycontainer.commons.regex.RegexUtil; public class HttpRawProtocol implements Closeable { public static enum State { CREATED, READY, SENDING_HEADERS, UPLOADING, READING_HEADERS, DOWNLOADING, CLOSED; public State check(State... states) { for (State s : states) { if (this.equals(s)) { return this; } } throw new RuntimeException("exp: " + Arrays.toString(states) + ", but was: " + this); } } private static final Pattern PATTERN_RESPONSE_LINE = Pattern .compile("^([^\\s]+) ([^\\s]+) ((.*))$"); private static final Pattern PATTERN_HEADER = Pattern .compile("^([^\\s:]+)[\\s:]+((.*))$"); private State state = State.CREATED; private Socket socket; private SocketFactory socketFactory = SocketFactory.getDefault(); private OutputStream outputStream = null; private byte[] lineFeed = "\n".getBytes(); private String protocolVersion; private Integer code; private String codeMessage; private PushbackInputStream inputStream; private String header; private String[] headerValues; public State getState() { return state; } public void connect(String address, int port) { try { state.check(State.CREATED); socket = socketFactory.createSocket(address, port); state = State.READY; } catch (UnknownHostException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } public void sendRequest(String method, String uri, String protocolVersion) { state.check(State.READY); sendLine(method, ' ', uri, ' ', protocolVersion); state = State.SENDING_HEADERS; } private void sendLine(Object... data) { try { OutputStream out = getOutputStream(); for (Object v : data) { String str = v.toString(); out.write(str.getBytes()); } out.write(lineFeed); } catch (IOException e) { throw new RuntimeException(e); } } private OutputStream getOutputStream() { if (outputStream == null) { try { outputStream = new BufferedOutputStream( socket.getOutputStream()); } catch (IOException e) { throw new RuntimeException(e); } } return outputStream; } public void sendHeader(String header, String... values) { state.check(State.SENDING_HEADERS); StringBuilder value = StringUtil.join(null, ",", values); sendLine(header, ": ", value); } public void sendHeaderFinished() { state.check(State.SENDING_HEADERS); sendLine(); state = State.UPLOADING; } public String readProtocolVersion() { state.check(State.UPLOADING, State.READING_HEADERS); flush(); state = State.READING_HEADERS; StringBuilder line = readLine(); if (line == null) { return null; } List<String> groups = RegexUtil.groups(PATTERN_RESPONSE_LINE, line); this.protocolVersion = groups.get(1).trim(); this.code = Integer.parseInt(groups.get(2).trim()); this.codeMessage = ""; if (groups.size() > 3) { this.codeMessage = groups.get(3).trim(); } return this.protocolVersion; } private void flush() { try { getOutputStream().flush(); } catch (IOException e) { throw new RuntimeException(e); } } protected StringBuilder readLine() { PushbackInputStream in = getInputStream(); StringBuilder ret = new StringBuilder(); try { while (true) { int read = in.read(); if (read < 0) { throw new RuntimeException("input closed"); } if (read == '\r') { continue; } if (read == '\n') { break; } ret.append((char) read); } return ret; } catch (SocketTimeoutException e) { try { in.unread(ret.toString().getBytes()); return null; } catch (IOException e1) { throw new RuntimeException(e); } } catch (IOException e) { throw new RuntimeException(e); } } public PushbackInputStream getInputStream() { if (this.inputStream == null) { try { this.inputStream = new PushbackInputStream( new BufferedInputStream(socket.getInputStream())); } catch (IOException e) { throw new RuntimeException(e); } } return this.inputStream; } public Integer readCode() { state.check(State.READING_HEADERS); return code; } public String readCodeMessage() { state.check(State.READING_HEADERS); return codeMessage; } public String readHeader() { state.check(State.READING_HEADERS); StringBuilder line = readLine(); if (line == null) { return null; } if (line.length() == 0) { state = State.DOWNLOADING; return null; } List<String> groups = RegexUtil.groups(PATTERN_HEADER, line); this.header = groups.get(1); String value = groups.get(2); this.headerValues = value.split(","); for (int i = 0; i < this.headerValues.length; i++) { this.headerValues[i] = this.headerValues[i].trim(); } return this.header; } public String[] readHeaderValues() { state.check(State.READING_HEADERS); return this.headerValues; } public void sendBytes(byte[] bytes) { try { state.check(State.UPLOADING); getOutputStream().write(bytes); } catch (IOException e) { throw new RuntimeException(e); } } public void close() { IOUtil.close(socket); state = State.CLOSED; } public void setSoTimeout(int l) { try { state.check(State.READY, State.SENDING_HEADERS, State.UPLOADING, State.READING_HEADERS, State.DOWNLOADING); socket.setSoTimeout(l); } catch (SocketException e) { throw new RuntimeException(e); } } }