使用Netty和Scala参与者的异步http请求 使用Netty和Scala参与者的异步http请求

使用Netty和Scala参与者的异步http请求 使用Netty和Scala参与者的异步http请求,scala,asynchronous,httprequest,netty,actor,Scala,Asynchronous,Httprequest,Netty,Actor,嘿,希望有人能帮我一下 我正在尝试使用Scala Actors和Netty.io库获取并发出异步http请求。(是的,我知道Scala演员不受欢迎,但这对我来说是一个学习练习) 我编写了一个actorHttpRequestActor,它以case类RequestPage(uri:uri)的形式接受消息 当它接收到消息时,它会创建发出http请求所需的Netty对象,我已经基于[HttpSnoopClient](http://static.netty.io/3.5/xref/org/jboss/n

嘿,希望有人能帮我一下

我正在尝试使用Scala Actors和Netty.io库获取并发出异步http请求。(是的,我知道Scala演员不受欢迎,但这对我来说是一个学习练习)

我编写了一个actor
HttpRequestActor
,它以case类RequestPage(uri:uri)的形式接受消息

当它接收到消息时,它会创建发出http请求所需的Netty对象,我已经基于[
HttpSnoopClient
](http://static.netty.io/3.5/xref/org/jboss/netty/example/http/snoop/HttpSnoopClient.html)例如

我创建了一个客户端,并将当前的actor实例传递给我的
ChannelPipelineFactory
实现,该实现还将actor传递给我的
SimpleChannelUpstreamHandler
实现,在这里我已经覆盖了
messageReceived
函数

actor实例作为侦听器传递,我使用
DefaultHttpRequest
类创建一个请求,并写入通道以发出请求

使用写入通道返回的
ChannelFuture
对象对actor对象进行阻塞调用。当调用处理程序类的
messagereceived
函数时,我将netty http请求的响应解析为字符串,将带有响应内容的消息发送回actor,并关闭通道

在future完成后,我的代码尝试向调用参与者发送一个回复,并接收http内容响应

代码可以工作,我可以得到一个回复,将其发送到我的actor实例,打印出内容,并向正在使用的actor实例发布资源发送消息

问题是当我测试它时,最初对actor的调用没有得到响应,线程只是保持打开状态

代码示例-HttpRequestActor 我的
httprequestor
类的代码

    import scala.actors.Actor
import java.net.{InetSocketAddress,URI}
import org.jboss.netty.handler.codec.http._
import org.jboss.netty.bootstrap.ClientBootstrap
import org.jboss.netty.channel.Channel
import org.jboss.netty.channel._
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory
import org.jboss.netty.channel.group.DefaultChannelGroup
import java.util.concurrent.{Executors,CancellationException}
import org.jboss.netty.util.CharsetUtil
import scala.concurrent.{ Promise, Future }
import scala.concurrent.ExecutionContext.Implicits.global

/**
 * @author mebinum
 *
 */
class HttpRequestActor extends Actor {
    //initialize response with default uninitialized value
    private var resp:Response = _
    private val executor = Executors.newCachedThreadPool
    private val executor2 = Executors.newCachedThreadPool
    private val factory = new NioClientSocketChannelFactory(
                          executor,
                          executor2);

    private val allChannels = new DefaultChannelGroup("httpRequester")

    def act = loop {
        react {
            case RequestPage(uri) => requestUri(uri)
            case Reply(msg) => setResponse(Reply(msg))
            case NoReply => println("didnt get a reply");setResponse(NoReply)
            case NotReadable => println("got a reply but its not readable");setResponse(NotReadable)
            case ShutDown => shutDown()
        }
    }

    private def requestUri(uri:URI) = {

      makeChannel(uri) map {
          channel => {
              allChannels.add(channel)
              val request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.toString)
              request.setHeader(HttpHeaders.Names.HOST, uri.getHost())
              request.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE)
              request.setHeader(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP)

              val writeFuture = channel.write(request).awaitUninterruptibly()

              FutureReactor !? writeFuture match {
                  case future : ChannelFuture => {
                      future.addListener(new ChannelFutureListener() {
                          def operationComplete(future:ChannelFuture) {
                              // Perform post-closure operation
                              println("current response is " + resp)
                              sendResponse("look ma I finished")
                          }
                      })
                      future.getChannel().close()
                  }
              }

              this ! ShutDown
          }
      }
      //thread ends only if you send a reply from here
      //println("this is final sender " + sender)
      //reply("I am the true end")
    }

    private def makeChannel(uri:URI) = {
      val scheme = Some(uri.getScheme()).getOrElse("http")
      val host = Some(uri.getHost()).getOrElse("localhost")

      val port = Utils.getPort(uri.getPort, uri.getScheme)

      // Set up the event pipeline factory.
      val client = new ClientBootstrap(factory)
      client.setPipelineFactory(new PipelineFactory(this))

      //get the promised channel
      val channel = NettyFutureBridge(client.connect(new InetSocketAddress(host, port)))
      channel  
    }

    private def setResponse(aResponse:Response) = resp = aResponse

    private def sendResponse(msg:String) = {
      println("Sending the response " + msg)
      reply(resp)
    }

    private def shutDown() = {
        println("got a shutdown message")
        val groupFuture = allChannels.close().awaitUninterruptibly()
        factory.releaseExternalResources()
    }

    override def exceptionHandler = {
      case e : CancellationException => println("The request was cancelled"); throw e
      case tr: Throwable => println("An unknown exception happened " + tr.getCause()); throw tr
    }
}



trait Response
case class RequestPage(url:URI)

case class Reply(content:String) extends Response
case object NoReply extends Response
case object NotReadable extends Response
case object ShutDown

object FutureReactor extends Actor{
  def act = //loop {
      react {
        case future: ChannelFuture => {
            if (future.isCancelled) {
                throw new CancellationException()
            }
            if (!future.isSuccess()) {
                future.getCause().printStackTrace()
                throw future.getCause()
            }
            if(future.isSuccess() && future.isDone()){
                future.getChannel().getCloseFuture().awaitUninterruptibly()
                reply(future)
            }
        }
      }
    //}
  this.start
}


class ClientHandler(listener:Actor) extends SimpleChannelUpstreamHandler {

  override def exceptionCaught( ctx:ChannelHandlerContext, e:ExceptionEvent){
    e.getCause().printStackTrace()
    e.getChannel().close();
    throw e.getCause()
  }

  override def messageReceived(ctx:ChannelHandlerContext,  e:MessageEvent) = {
        var contentString = ""
        var httpResponse:Response =  null.asInstanceOf[Response]

        e.getMessage match {
          case (response: HttpResponse) if !response.isChunked => {
              println("STATUS: " + response.getStatus);
              println("VERSION: " + response.getProtocolVersion);
              println

              val content = response.getContent();
              if (content.readable()) {
                  contentString = content.toString(CharsetUtil.UTF_8)
                  httpResponse = Reply(contentString)
                  //notify actor

              }else{
                 httpResponse = NotReadable
              }
          }
          case chunk: HttpChunk if !chunk.isLast => {
            //get chunked content
            contentString = chunk.getContent().toString(CharsetUtil.UTF_8)
            httpResponse = Reply(contentString)
          }
          case _ => httpResponse = NoReply
        }
         println("sending actor my response")
         listener ! httpResponse
         println("closing the channel")
         e.getChannel().close()
         //send the close event

    }


}


class PipelineFactory(listener:Actor) extends ChannelPipelineFactory {

    def  getPipeline(): ChannelPipeline = {
            // Create a default pipeline implementation.
            val pipeline = org.jboss.netty.channel.Channels.pipeline()

            pipeline.addLast("codec", new HttpClientCodec())

            // Remove the following line if you don't want automatic content decompression.
            pipeline.addLast("inflater", new HttpContentDecompressor())

            // Uncomment the following line if you don't want to handle HttpChunks.
            //pipeline.addLast("aggregator", new HttpChunkAggregator(1048576))

            pipeline.addLast("decoder", new HttpRequestDecoder())
            //assign the handler
            pipeline.addLast("handler", new ClientHandler(listener))

            pipeline;
    }
}


object NettyFutureBridge { 
  import scala.concurrent.{ Promise, Future }
  import scala.util.Try
  import java.util.concurrent.CancellationException 
  import org.jboss.netty.channel.{ Channel, ChannelFuture, ChannelFutureListener }

  def apply(nettyFuture: ChannelFuture): Future[Channel] = { 
    val p = Promise[Channel]() 
    nettyFuture.addListener(new ChannelFutureListener { 
      def operationComplete(future: ChannelFuture): Unit = p complete Try( 
        if (future.isSuccess) {
          println("Success")
          future.getChannel
        }
        else if (future.isCancelled) {
          println("Was cancelled")
          throw new CancellationException 
        }

        else {
          future.getCause.printStackTrace()
          throw future.getCause
        })
    }) 
    p.future 
  }
} 
代码来测试它

val url = "http://hiverides.com"

test("Http Request Actor can recieve and react to message"){
    val actor = new HttpRequestActor()
    actor.start

    val response = actor !? new RequestPage(new URI(url)) 
    match {
      case Reply(msg) => {
          println("this is the reply response in test")
          assert(msg != "")
          println(msg)
        }
      case NoReply => println("Got No Reply")
      case NotReadable => println("Got a not Reachable")
      case None => println("Got a timeout")
      case s:Response => println("response string \n" + s)
      case x => {println("Got a value not sure what it is"); println(x);}

    }
  }
使用的图书馆: -Scala 2.9.2 -Netty.io 3.6.1.Final -JUnit4.7 -缩放测试1.8 -我还使用NettyFutureBridge对象为返回的通道对象创建scala未来

我如何向actor对象发送带有Netty响应内容的回复并结束线程


我不知道Scala,但我也遇到过类似的问题。尝试指定响应的内容长度标题

在纯java中:

HttpRequest r = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uri);
            ChannelBuffer buffer = ChannelBuffers.copiedBuffer(input);
            r.setHeader(HttpHeaders.Names.HOST, "host");
            r.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/octet-stream");
            r.setHeader(HttpHeaders.Names.CONTENT_LENGTH, buffer.readableBytes());
            r.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE);
            r.setContent(buffer);
否则,服务器不知道内容何时从客户端完成,除非客户端关闭连接


您也可以使用分块编码,但您必须自己实现分块编码(至少我不知道Netty中有哪个库可以实现)。

如果您还不知道,请查看链接Dylan,该库看起来很全面,我仍然想要一个简单的解决方案,为了真正理解我做错了什么,我即将签入一些使用Netty和Scala 2.10 futures的代码。它经过测试,可以工作,但不使用演员。然而,在这种情况下,也许它可以帮助解决这个问题。完成后我会告诉你的。同时,我建议使用Wireshark或类似工具来查看电线上发生了什么。嘿,山姆,那太棒了。我一直在考虑切换到Akka的想法,我想这是wat 2.10的用法,请告诉我你什么时候有了它。谢谢@aaron的回复,尝试了一下,但没有什么不同。我认为问题实际上可能与我的Scala actor代码有关,而不是与我的netty实现有关