HTTPプロキシ練習その2
代行したHTTPアクセスをSocketを使用したLowレベルなものにして、
リダイレクションに対応させてみた。
しかし、まだHeaderのやりとりをちゃんと実装していのでまともに動かない。
あと、バイナリファイルが壊れる。
package proxy import io._ import scala.actors._ import java.net._ import java.io._ import collection.mutable.ArrayBuffer import collection.mutable.Map // html browser case class Browser(socket :Socket, index :Int) // http response case object Response{ 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 HttpClient(url :URL) { println("proxy connect: "+url.getHost) val socket=new Socket(url.getHost, if(url.getPort== -1){ url.getDefaultPort } else{ url.getPort }) val in=socket.getInputStream val out=new DataOutputStream(socket.getOutputStream) var isEnd=false var proxyResponseHeader=Map[String, String]() private def get() :Byte={ val buf=new Array[Byte](1) val readSize=in.read(buf, 0, 1) if(readSize == -1){ isEnd=true throw new IOException } buf(0) } def getLine() :String={ val buf=new ArrayBuffer[Byte] try { var endOfLine=false while(!endOfLine){ get() match { case '\n'=> endOfLine=true case '\r'=> 0 case b => buf.append(b) } } } catch{ case e :IOException=> 0 } new String(buf.toArray) } private def readheader(){ var inHeader=true while(inHeader){ val line=getLine() if(line==""){ inHeader=false } else{ line match { case Response.HeaderPattern(key, value) => println(key, value) proxyResponseHeader+=(key.toLowerCase() -> value) } } } } def run(socket :Socket) :Int={ // request val request="GET "+url.getPath+" HTTP/1.0" println("proxy request: "+request) out.writeBytes(request+"\r\n") out.writeBytes("\r\n") out.flush() // read header val firstline=getLine() firstline match { case Response.SuccessPattern(version, code, message)=> readheader 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") while(!isEnd){ out.writeBytes(getLine()) } code toInt case Response.RedirectPattern(version, code, message)=> readheader code toInt case Response.Pattern => println("no match: "+firstline) 0 } } } class Resolver(maxRedirection :Int) extends Actor { private def get(socket :Socket, index :Int, url :String, version :String){ var redirectRemain=maxRedirection var redirect=0 var target=new URL(url) while(redirectRemain>0){ println("["+index+":"+redirect+"] browser GET: "+url+" "+version) val c=new HttpClient(target) c.run(socket) match { case 302=> redirectRemain-=1 if(redirectRemain<0){ println("abort redirection") } else{ redirect+=1 target=new URL(c.proxyResponseHeader("location")) } case _=> redirectRemain=0 } } socket.close() } private def request(socket :Socket, index :Int){ val iter=Source.fromInputStream(socket.getInputStream()).getLines iter.next.split(' ') match { case Array("GET", url, version)=>get(socket, index, url, version) case _ => 0 } socket.close() } def act(){ while(true){ receive { case Browser(socket, i)=>request(socket, i) } } } } class Server(port :Int) { def run() { println("run") 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(3); 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.run } }