high performance http server writen by akka

采用akka2.0 IO ByteString相关技术,代码改自http://doc.akka.io/docs/akka/2.0/scala/io.html,目前代码比较粗糙,但性能已经体现出来了。

 

话不多说,贴代码

 

 

Scala代码 
  1. /**  
  2.  * Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>  
  3.  */  
  4. package akka.docs.io.v2  
  5.   
  6. //#imports  
  7. import akka.actor._  
  8. import akka.util.{ ByteString, ByteStringBuilder }  
  9. import java.net.InetSocketAddress  
  10. //#imports  
  11.   
  12. //#actor  
  13. class HttpServer2(port: Int) extends Actor {  
  14.   
  15.   val state = new scala.collection.mutable.HashMap[IO.Handle, ActorRef]()  
  16.   
  17.   override def preStart {  
  18.     IOManager(context.system) listen new InetSocketAddress(port)  
  19.   }  
  20.   
  21.   def receive = {  
  22.   
  23.     case IO.NewClient(server) ⇒  
  24.       val socket = server.accept()  
  25.       val worker = context.actorOf(Props(new Worker(socket)))  
  26.       state(socket) = worker  
  27.       state(socket) ! socket  
  28.   
  29.     case IO.Read(socket, bytes) ⇒  
  30.       state(socket) ! IO.Read(socket, bytes)  
  31.   
  32.     case IO.Closed(socket, cause) ⇒  
  33.       state(socket) ! IO.Closed(socket, cause)  
  34.       state -= socket  
  35.   
  36.   }  
  37.   
  38. }  
  39.   
  40. class Worker(socket: IO.SocketHandle) extends Actor {  
  41.   
  42.   val state = IO.IterateeRef.Map.async[IO.Handle]()(context.dispatcher)  
  43.   
  44.   override def preStart {  
  45.     // state(socket) flatMap (_ ⇒ HttpServer2.processRequest(socket))  
  46.   }  
  47.   
  48.   def receive = {  
  49.   
  50.     case socket:IO.SocketHandle ⇒  
  51.       state(socket) flatMap (_ ⇒ HttpServer2.processRequest(socket))  
  52.   
  53.     case IO.Read(socket, bytes) ⇒  
  54.       state(socket)(IO Chunk bytes)  
  55.   
  56.     case IO.Closed(socket, cause) ⇒  
  57.       state(socket)(IO EOF None)  
  58.       state -= socket  
  59.   
  60.   }  
  61.   
  62. }  
  63.   
  64. //#actor  
  65.   
  66. //#actor-companion  
  67. object HttpServer2 {  
  68.   import HttpIteratees._  
  69.   
  70.   def processRequest(socket: IO.SocketHandle): IO.Iteratee[Unit] =  
  71.     IO repeat {  
  72.       for {  
  73.         request ← readRequest  
  74.       } yield {  
  75.         val rsp = request match {  
  76.           case Request("GET""ping" :: Nil, _, _, headers, _) ⇒  
  77.             OKResponse(ByteString("<p>pong</p>"),  
  78.               request.headers.exists { case Header(n, v) ⇒ n.toLowerCase == "connection" && v.toLowerCase == "keep-alive" })  
  79.           case req ⇒  
  80.             OKResponse(ByteString("<p>" + req.toString + "</p>"),  
  81.               request.headers.exists { case Header(n, v) ⇒ n.toLowerCase == "connection" && v.toLowerCase == "keep-alive" })  
  82.         }  
  83.         socket write OKResponse.bytes(rsp).compact  
  84.         if (!rsp.keepAlive) socket.close()  
  85.       }  
  86.     }  
  87.   
  88. }  
  89. //#actor-companion  
  90.   
  91. //#request-class  
  92. case class Request(meth: String, path: List[String], query: Option[String], httpver: String, headers: List[Header], body: Option[ByteString])  
  93. case class Header(name: String, value: String)  
  94. //#request-class  
  95.   
  96. //#constants  
  97. object HttpConstants {  
  98.   val SP = ByteString(" ")  
  99.   val HT = ByteString("\t")  
  100.   val CRLF = ByteString("\r\n")  
  101.   val COLON = ByteString(":")  
  102.   val PERCENT = ByteString("%")  
  103.   val PATH = ByteString("/")  
  104.   val QUERY = ByteString("?")  
  105. }  
  106. //#constants  
  107.   
  108. //#read-request  
  109. object HttpIteratees {  
  110.   import HttpConstants._  
  111.   
  112.   def readRequest =  
  113.     for {  
  114.       requestLine ← readRequestLine  
  115.       (meth, (path, query), httpver) = requestLine  
  116.       headers ← readHeaders  
  117.       body ← readBody(headers)  
  118.     } yield Request(meth, path, query, httpver, headers, body)  
  119.   //#read-request  
  120.   
  121.   //#read-request-line  
  122.   def ascii(bytes: ByteString): String = bytes.decodeString("US-ASCII").trim  
  123.   
  124.   def readRequestLine =  
  125.     for {  
  126.       meth ← IO takeUntil SP  
  127.       uri ← readRequestURI  
  128.       _ ← IO takeUntil SP // ignore the rest  
  129.       httpver ← IO takeUntil CRLF  
  130.     } yield (ascii(meth), uri, ascii(httpver))  
  131.   //#read-request-line  
  132.   
  133.   //#read-request-uri  
  134.   def readRequestURI = IO peek 1 flatMap {  
  135.     case PATH ⇒  
  136.       for {  
  137.         path ← readPath  
  138.         query ← readQuery  
  139.       } yield (path, query)  
  140.     case _ ⇒ sys.error("Not Implemented")  
  141.   }  
  142.   //#read-request-uri  
  143.   
  144.   //#read-path  
  145.   def readPath = {  
  146.     def step(segments: List[String]): IO.Iteratee[List[String]] = IO peek 1 flatMap {  
  147.       case PATH ⇒ IO drop 1 flatMap (_ ⇒ readUriPart(pathchar) flatMap (segment ⇒ step(segment :: segments)))  
  148.       case _ ⇒ segments match {  
  149.         case "" :: rest ⇒ IO Done rest.reverse  
  150.         case _          ⇒ IO Done segments.reverse  
  151.       }  
  152.     }  
  153.     step(Nil)  
  154.   }  
  155.   //#read-path  
  156.   
  157.   //#read-query  
  158.   def readQuery: IO.Iteratee[Option[String]] = IO peek 1 flatMap {  
  159.     case QUERY ⇒ IO drop 1 flatMap (_ ⇒ readUriPart(querychar) map (Some(_)))  
  160.     case _     ⇒ IO Done None  
  161.   }  
  162.   //#read-query  
  163.   
  164.   //#read-uri-part  
  165.   val alpha = Set.empty ++ ('a' to 'z') ++ ('A' to 'Z') map (_.toByte)  
  166.   val digit = Set.empty ++ ('0' to '9') map (_.toByte)  
  167.   val hexdigit = digit ++ (Set.empty ++ ('a' to 'f') ++ ('A' to 'F') map (_.toByte))  
  168.   val subdelim = Set('!''$''&''\'''('')''*''+'','';''=') map (_.toByte)  
  169.   val pathchar = alpha ++ digit ++ subdelim ++ (Set(':''@') map (_.toByte))  
  170.   val querychar = pathchar ++ (Set('/''?') map (_.toByte))  
  171.   
  172.   def readUriPart(allowed: Set[Byte]): IO.Iteratee[String] = for {  
  173.     str ← IO takeWhile allowed map ascii  
  174.     pchar ← IO peek 1 map (_ == PERCENT)  
  175.     all ← if (pchar) readPChar flatMap (ch ⇒ readUriPart(allowed) map (str + ch + _)) else IO Done str  
  176.   } yield all  
  177.   
  178.   def readPChar = IO take 3 map {  
  179.     case Seq('%', rest @ _*) if rest forall hexdigit ⇒  
  180.       java.lang.Integer.parseInt(rest map (_.toChar) mkString, 16).toChar  
  181.   }  
  182.   //#read-uri-part  
  183.   
  184.   //#read-headers  
  185.   def readHeaders = {  
  186.     def step(found: List[Header]): IO.Iteratee[List[Header]] = {  
  187.       IO peek 2 flatMap {  
  188.         case CRLF ⇒ IO takeUntil CRLF flatMap (_ ⇒ IO Done found)  
  189.         case _    ⇒ readHeader flatMap (header ⇒ step(header :: found))  
  190.       }  
  191.     }  
  192.     step(Nil)  
  193.   }  
  194.   
  195.   def readHeader =  
  196.     for {  
  197.       name ← IO takeUntil COLON  
  198.       value ← IO takeUntil CRLF flatMap readMultiLineValue  
  199.     } yield Header(ascii(name), ascii(value))  
  200.   
  201.   def readMultiLineValue(initial: ByteString): IO.Iteratee[ByteString] = IO peek 1 flatMap {  
  202.     case SP ⇒ IO takeUntil CRLF flatMap (bytes ⇒ readMultiLineValue(initial ++ bytes))  
  203.     case _  ⇒ IO Done initial  
  204.   }  
  205.   //#read-headers  
  206.   
  207.   //#read-body  
  208.   def readBody(headers: List[Header]) =  
  209.     if (headers.exists(header ⇒ header.name == "Content-Length" || header.name == "Transfer-Encoding"))  
  210.       IO.takeAll map (Some(_))  
  211.     else  
  212.       IO Done None  
  213.   //#read-body  
  214. }  
  215.   
  216. //#ok-response  
  217. object OKResponse {  
  218.   import HttpConstants.CRLF  
  219.   
  220.   val okStatus = ByteString("HTTP/1.1 200 OK")  
  221.   val contentType = ByteString("Content-Type: text/html; charset=utf-8")  
  222.   val cacheControl = ByteString("Cache-Control: no-cache")  
  223.   val date = ByteString("Date: ")  
  224.   val server = ByteString("Server: Akka")  
  225.   val contentLength = ByteString("Content-Length: ")  
  226.   val connection = ByteString("Connection: ")  
  227.   val keepAlive = ByteString("Keep-Alive")  
  228.   val close = ByteString("Close")  
  229.   
  230.   def bytes(rsp: OKResponse) = {  
  231.     new ByteStringBuilder ++=  
  232.       okStatus ++= CRLF ++=  
  233.       contentType ++= CRLF ++=  
  234.       cacheControl ++= CRLF ++=  
  235.       date ++= ByteString(new java.util.Date().toString) ++= CRLF ++=  
  236.       server ++= CRLF ++=  
  237.       contentLength ++= ByteString(rsp.body.length.toString) ++= CRLF ++=  
  238.       connection ++= (if (rsp.keepAlive) keepAlive else close) ++= CRLF ++= CRLF ++= rsp.body result  
  239.   }  
  240.   
  241. }  
  242. case class OKResponse(body: ByteString, keepAlive: Boolean)  
  243. //#ok-response  
  244.   
  245. //#main  
  246. object Main extends App {  
  247.   val port = Option(System.getenv("PORT")) map (_.toInt) getOrElse 8080  
  248.   val system = ActorSystem()  
  249.   val server = system.actorOf(Props(new HttpServer2(port)))  
  250. }  
  251. //#main  

posted on 2013-06-03 09:08  Bo.Zhang  阅读(602)  评论(0编辑  收藏  举报