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
    }
}