我正在尝试使用Spray / Akka实现反向HTTP代理,但遇到了麻烦。我发现在某些情况下,即使客户端断开连接,我的代理服务器也会继续从上游服务器接收数据。
以下是我如何实现我的Spray代理指令(只是对bthuillier's implementation的一点修改):
trait ProxyDirectives {
private def sending(f: RequestContext ⇒ HttpRequest)(implicit system: ActorSystem): Route = {
val transport = IO(Http)(system)
ctx ⇒ transport.tell(f(ctx), ctx.responder)
}
/**
* Re-shape the original request, to match the destination server.
*/
private def reShapeRequest(req: HttpRequest, uri: Uri): HttpRequest = {
req.copy(
uri = uri,
headers = req.headers.map {
case x: HttpHeaders.Host => HttpHeaders.Host(uri.authority.host.address, uri.authority.port)
case x => x
}
)
}
/**
* proxy the request to the specified uri
*
*/
def proxyTo(uri: Uri)(implicit system: ActorSystem): Route = {
sending(ctx => reShapeRequest(ctx.request, uri))
}
}
如果我在客户端和服务器之间放置一个代理层(即客户端< - > proxyTo< - >服务器),这个反向代理将很好用,但是如果我放两个层就会有问题客户端和服务器之间。例如,如果我有以下简单的Python HTTP服务器:
import socket
from threading import Thread, Semaphore
import time
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from SocketServer import ThreadingMixIn
class MyHTTPHandler(BaseHTTPRequestHandler):
protocol_version = 'HTTP/1.1'
def do_GET(self):
self.send_response(200)
self.send_header('Transfer-Encoding', 'chunked')
self.end_headers()
for i in range(100):
data = ('%s\n' % i).encode('utf-8')
self.wfile.write(hex(len(data))[2:].encode('utf-8'))
self.wfile.write(b'\r\n')
self.wfile.write(data)
self.wfile.write(b'\r\n')
time.sleep(1)
self.wfile.write(b'0\r\n\r\n')
class MyServer(ThreadingMixIn, HTTPServer):
def server_bind(self):
HTTPServer.server_bind(self)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
def server_close(self):
HTTPServer.server_close(self)
if __name__ == '__main__':
server = MyServer(('127.0.0.1', 8080), MyHTTPHandler)
server.serve_forever()
这基本上什么也没做,只是打开一个分块响应(对于长期运行,以便我们可以检查问题)。如果我按以下方式链接两层代理:
class TestActor(val target: String)(implicit val system: ActorSystem) extends Actor
with HttpService
with ProxyDirectives
{
// we use the enclosing ActorContext's or ActorSystem's dispatcher for our Futures and Scheduler
implicit private def executionContext = actorRefFactory.dispatcher
// the HttpService trait defines only one abstract member, which
// connects the services environment to the enclosing actor or test
def actorRefFactory = context
val serviceRoute: Route = {
get {
proxyTo(target)
}
}
// runs the service routes.
def receive = runRoute(serviceRoute) orElse handleTimeouts
private def handleTimeouts: Receive = {
case Timedout(x: HttpRequest) =>
sender ! HttpResponse(StatusCodes.InternalServerError, "Request timed out.")
}
}
object DebugMain extends App {
val actorName = "TestActor"
implicit val system = ActorSystem(actorName)
// create and start our service actor
val service = system.actorOf(
Props { new TestActor("http://127.0.0.1:8080") },
s"${actorName}Service"
)
val service2 = system.actorOf(
Props { new TestActor("http://127.0.0.1:8081") },
s"${actorName}2Service"
)
IO(Http) ! Http.Bind(service, "::0", port = 8081)
IO(Http) ! Http.Bind(service2, "::0", port = 8082)
}
使用curl http://localhost:8082
连接到代理服务器,即使在curl被杀死后你也会看到Akka系统继续传输数据(你可以打开DEBUG级别的日志来查看详细信息)。
我该如何处理这个问题?感谢。
答案 0 :(得分:0)
嗯,事实证明这是一个非常复杂的问题,而我的解决方案需要近100行代码。
实际上,当我堆叠两层代理时,问题不仅存在。当我使用单层代理时,问题确实存在,但没有打印日志,所以我之前没有发现过这个问题。
关键问题是,虽然我们使用 import UIKit
class ViewController: UIViewController {
@IBOutlet weak var textfield1: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var DestViewController : ViewTwo = segue.destinationViewController as ViewTwo // here is where I get the error
}
,但它实际上是来自spray-can的主机级API。主机级API的连接由Spray IO(Http) ! HttpRequest
管理,我们的代码无法访问它们。因此,我们无法对该连接执行任何操作,除非我们向HttpManager
发送Http.CloseAll
,这将导致所有上游连接关闭。
(如果有人知道如何从IO(Http)
获取连接,请告诉我。)
我们必须使用spray-can的连接级API来满足这种情况。所以我想出了类似的东西:
HttpManager
代码很长,我怀疑应该有一些更干净和简单的实现(实际上我不熟悉Akka)。不过,这段代码有效,所以我把这个解决方案放在这里。如果您找到了更好的解决方案,可以自由发布解决方案。