HTTPプロキシ練習その3
ある程度動くところまでできた。
やな感じにコードが増えつつある。
まだ、画像が途中で切れる(Content-lengthないからか?)などの問題がある。
現状での一番のはまりポイントは、HTTPヘッダのHost:指定だった。
apacheとかの名前ベースVirtualHost(多分)率がわりと高くて
Host:を書いてないとすぐに404とか403になってしまい、何故だか判らずはまってしまった。
せっかくなので、ログ表示を詳しくしてactorとthreadの動きがどうなっているのかを見てみるか。
package proxy import io._ import scala.actors._ import java.net.URL import java.net.ServerSocket import java.net.Socket case class Browser(socket :Socket, index :Int) class Resolver(maxRedirection :Int) extends Actor { def act(){ loop{ receive { case Browser(client, index)=> val iter=Source.fromInputStream( client.getInputStream()).getLines iter.next.split(' ') match { case Array("GET", url, version)=> val c=new HTTPClient(client, index, version) c.get(url) case Array("POST", url, version)=> val c=new HTTPClient(client, index, version) c.post(url) case _ => 0 } client.close() } } } } class Server(port :Int) { def start() { val server = new ServerSocket(port) println("server: Waiting for connection: "+port) var count=0 def inc(n: Int): Stream[Int]=Stream.cons(n, inc(n+1)) for(i <- inc(0)){ val socket=server.accept() val resolver= new Resolver(5) resolver.start() resolver ! Browser(socket, i) } } } object Server { def main(args :Array[String]){ val p=new Server(if(args.length>0){ args(0) toInt } else{ 9998 }) p.start() } }
package proxy import collection.mutable.ArrayBuffer import collection.mutable.Map import java.net.Socket import java.net.URL import java.net.MalformedURLException import java.io.DataOutputStream import java.io.IOException import java.io.DataOutputStream // http response case object HTTPResponse{ val SuccessPattern="""^(\S+)\s+(2\d\d)\s+(.*)""".r val RedirectPattern="""^(\S+)\s+(3\d\d)\s+(.*)""".r val Pattern="""^(\S+)\s+(\d\d\d)\s+(.*)""".r val HeaderPattern="""([^:]+):\s*(.*)""".r } class ProxyConnection(url :URL){ class ProxyRequest(method :String, url :URL, version :String){ def send(out :DataOutputStream){ val request=if(url.getQuery==null){ method+" "+url.getPath+" HTTP/"+version } else{ method+" "+url.getPath+"?"+url.getQuery+" HTTP/"+version } println("proxy request: "+request) out.writeBytes(request+"\r\n") out.writeBytes("Host: "+url.getHost+"\r\n") } } println("proxy connect: "+url.getHost) private val socket=new Socket(url.getHost, if(url.getPort== -1){ url.getDefaultPort } else{ url.getPort }) private val buf=new Array[Byte](1) private val in=socket.getInputStream private val out=new DataOutputStream(socket.getOutputStream) private var isEnd=false var proxyResponseHeader=Map[String, String]() private def getChar() :Byte={ val readSize=in.read(buf, 0, 1) if(readSize == -1){ isEnd=true throw new IOException } buf(0) } private def getLine() :Array[Byte]={ val buf=new ArrayBuffer[Byte] try { var endOfLine=false while(!endOfLine){ getChar() match { case '\n'=> endOfLine=true case '\r'=> 0 case b => buf.append(b) } } } catch{ case e :IOException=> 0 } buf.toArray } private def readHeader(){ var inHeader=true while(inHeader){ val line=new String(getLine()) if(line==""){ inHeader=false } else{ line match { case HTTPResponse.HeaderPattern(key, value) => proxyResponseHeader+=(key.toLowerCase() -> value) } } } } private def getData(size :Int) :(Int, Array[Byte])={ if(isEnd){ return (0 -> new Array[Byte](0)) } val buf=new Array[Byte](size) val readSize=in.read(buf, 0, buf.length) if(readSize == -1){ isEnd=true return (0 -> new Array[Byte](0)) } return (readSize -> buf) } private def publishBody(socket :Socket){ val out=new DataOutputStream(socket.getOutputStream()) out.writeBytes("HTTP/1.0 200 OK\r\n") out.writeBytes("Content-Type: "+ proxyResponseHeader("content-type")+"\r\n") out.writeBytes("\r\n") if(proxyResponseHeader.contains("content-length")){ val (size, buf)=getData( proxyResponseHeader("content-length") toInt) out.write(buf, 0, size) } else{ while(!isEnd){ val (size, buf)=getData(1024) out.write(buf, 0, size) } } out.flush() socket.close() } def get(client :Socket) :Boolean={ // send request var header=new ProxyRequest("GET", url, "1.0") header.send(out) out.writeBytes("\r\n") out.flush() // read header val firstline=new String(getLine()) firstline match { case HTTPResponse.SuccessPattern(version, code, message)=> println(code, message) readHeader() publishBody(client) false case HTTPResponse.RedirectPattern(version, code, message)=> println(code, message) readHeader() if(proxyResponseHeader("location")== "http://"+url.getHost+url.getPath){ println("infinity loop") false } else{ true } case HTTPResponse.Pattern(version, code, message)=> println(code, message) false case _ => println("no match: "+firstline) false } } } class HTTPClient(client :Socket, index :Int, version :String) { def get(url :String){ var redirect=0 var target=new URL(url) val maxRedirection=3 while(redirect<maxRedirection){ println("["+index+":"+redirect+"] GET: "+target+" "+version) val c=new ProxyConnection(target) val isRedirected=c.get(client) if(isRedirected){ // rdcirection redirect+=1 val location=c.proxyResponseHeader("location") println("Location: "+location) try{ target=if(target.getQuery==null){ new URL(location) } else{ new URL(location + "?" + target.getQuery()) } } catch{ case e :MalformedURLException=> target=if(target.getQuery==null){ new URL( "http://"+target.getHost()+location) } else{ new URL( "http://"+target.getHost()+location+ "?"+target.getQuery()) } } if(redirect>=maxRedirection){ println("abort redirection") } } else{ // no redirection redirect=maxRedirection } } } def post(url :String){ "not implemented" } }