如何避免,如果在Android的一个HTTPGET操作持续太久收到错误10053(WSAECONNABORTED)?太久、错误、操作、HTTPGET

2023-09-07 12:36:55 作者:蘇辭

我有一个用印10 TIdHttpServer一个Delphi 2006的Web服务应用程序的Andr​​oid应用程序通讯(与德尔福2006年到来)。德尔福应用程序生成一个大的XML文件,并服务于这个。 XML生成可能持续超过5分钟。

I have an Android application communicating with a Delphi 2006 web service application using Indy 10 TIdHttpServer (coming with Delphi 2006). The Delphi application generates a big XML file and serves this. The XML generation may last more than 5 minutes.

如果持续时间 GenerateXml()超过约5分钟(*),我发现一个错误10053 TIdHTT presponseInfo .WriteContent 如果运行在Delphi IDE:

If the duration of GenerateXml() is more than about 5 minutes (*), I detect an error 10053 in TIdHTTPResponseInfo.WriteContent if running in the Delphi IDE:

Socket Error # 10053 Software caused connection abort.

不过,在Android端没有被检测到,并在 HTTPGET -call永远持续。

However, on the android side nothing is detected and the HttpGet-call lasts forever.

我的问题是:

1)为什么我得到错误10053,我怎么能避免呢?它看起来像机器人的连接超时,但 http.socket.timeout 设置为无穷大。

1.) Why do I get the error 10053 and how can I avoid it? It seems like android times out the connection, but http.socket.timeout is set to infinite.

2。)我​​能做些什么来检测在客户端(比设置超时等,这将有太大有用这样的错误)?我可以做一些TId​​HttpServer.OnException?

2.) What can I do to detect such an error on the client side (other than setting timeout, which would have to be too big to be useful)? Can I do something in TIdHttpServer.OnException?

下面是我的code。 Android的 - 下载功能,这是一个AsyncTask的内部运行:

Here is my code. Android - download function, which is run inside an AsyncTask:

protected static HttpEntity downloadEntity(String url) throws IOException {
    HttpClient client = new DefaultHttpClient();  

    //Check because of Error 10053: but timeout is null -> infinite
    Log.d("TAG", "http.socket.timeout: " + client.getParams().getParameter("http.socket.timeout"));

    HttpGet get = new HttpGet(url);
    HttpResponse response;
    try {
        //in case of Error 10053 the following call seems to last forever (in PlainSocketImpl.read)
        response = client.execute(get);
    } catch (ClientProtocolException e) {
        //...
    }

    //...

    return response.getEntity();  
}   

Delphi实现TIdHttpServer.OnCommandGet的:

Delphi implementation of TIdHttpServer.OnCommandGet:

procedure ServeXmlDoc(XmlDoc: IXMLDocument; ResponseInfo: TIdHTTPResponseInfo);
var
    TempStream: TMemoryStream;
begin
    ResponseInfo.ContentType := 'text/xml';
    TempStream := TMemoryStream.Create;
    XMLDoc.SaveToStream(TempStream);
    ResponseInfo.FreeContentStream := True; 
    ResponseInfo.ContentStream := TempStream;
end;

procedure TMyService.HTTPServerCommandGet(AContext: TIdContext; RequestInfo: TIdHTTPRequestInfo;
  ResponseInfo: TIdHTTPResponseInfo);
begin
    Coinitialize(nil); 
    try
        //...
        ServeXmlDoc(GenerateXml(), ResponseInfo);
    finally
        CoUninitialize;
    end;
end;

编辑:(*)我已经做了进一步的测试,甚至在情况下,整个过程有不到2分钟的持续时间所经历的误差

(*) I have done further testing and experienced the error even in cases where the whole process had a duration of under 2 minutes.

推荐答案

Android和你的服务器,例如防火墙/路由器之间的事情,很可能切断连接后,它是闲置太久。你应该尝试启用TCP保持有效指示,以避免这一点。

Something between Android and your server, such as a firewall/router, is likely cutting the connection after it is idle for too long. You should try enabling TCP keep-alives to avoid that.

在另一方面,这是什么样的情况HTTP 1.1的 被设计来处理(假设你使用HTTP 1.1开始)块传输编码。与其等待前5分钟,然后将其发送给客户端的全要生成的整个XML,你应该发送XML块中的正在生成它们。不仅是保持连接活跃,但它也降低了服务器的内存占用量,因为它不必在存储器中的整个XML存储在同一时间。

On the other hand, this is the kind of situation that HTTP 1.1's chunked transfer encoding was designed to handle (assuming you are using HTTP 1.1 to begin with). Instead of waiting 5 minutes for the entire XML to be generated in full before then sending it to the client, you should send the XML in pieces as they are being generated. Not only does that keep the connection active, but it also reduces the server's memory footprint since it doesn't have to store the entire XML in memory at one time.

TIdHTTPServer 没有(还)原生支持发送分块响应(但 TIdHTTP 不支持接收分块响应),但是它不会是很困难的手动实施。编写自定义 TStream 派生类,并覆盖其虚拟写()方法(或使用Indy的 TIdEventStream 类)使用中的 2616第3.6.1节。有了这一点,你可以有 ServeXmlDoc()设置 ResponseInfo.TransferEncoding 属性分块并调用 ResponseInfo.WriteHeader()方法,无需设置任何的 ResponseInfo.ContentText ResponseInfo.ContentStream 属性,然后通过你的自定义流 IXMLDocument.SaveToStream()这样就写完响应头后的数据。例如:

TIdHTTPServer does not (yet) natively support sending chunked responses (but TIdHTTP does support receiving chunked responses), however it would not be very difficult to implement manually. Write a custom TStream derived class and overwrite its virtual Write() method (or use Indy's TIdEventStream class) to write data to the HTTP client using the format outlined in RFC 2616 Section 3.6.1. With that, you can have ServeXmlDoc() set the ResponseInfo.TransferEncoding property to 'chunked' and call the ResponseInfo.WriteHeader() method without setting either the ResponseInfo.ContentText or ResponseInfo.ContentStream properties, then pass your custom stream to IXMLDocument.SaveToStream() so it will finish writing the response data after the headers. For example:

type
  TMyChunkedStream = class(TStream)
  private
    fIO: TIdIOHandler;
  public
    constructor Create(AIO: TIdIOHandler);
    function Write(const Buffer; Count: Longint): Longint; override;
    procedure Finished;
    ...
  end;

constructor TMyChunkedStream.Create(AIO: TIdIOHandler);
begin
  inherited Create;
  fIO := AIO;
end;

function TMyChunkedStream.Write(const Buffer; Count: Longint): Longint; override;
begin
  if Count > 0 then
  begin
    fIO.WriteLn(IntToHex(Count, 1));
    fIO.Write(RawToBytes(Buffer, Count));
    fIO.WriteLn;
  end;
  Result := Count;
end;

procedure TMyChunkedStream.Finished;
begin
  fIO.WriteLn('0');
  fIO.WriteLn;
end;

procedure ServeXmlDoc(XmlDoc: IXMLDocument; ResponseInfo: TIdHTTPResponseInfo);
var
  TempStream: TMyChunkedStream;
begin
  ResponseInfo.ContentType := 'text/xml';
  ResponseInfo.TransferEncoding := 'chunked';
  ResponseInfo.WriteHeader;

  TempStream := TMyChunkedStream.Create(ResponseInfo.Connection.IOHandler);
  try
    XMLDoc.SaveToStream(TempStream);
    TempStream.Finished;
  finally
    TempStream.Free;
  end;
end;

如果,在另一方面,你的大部分等候的是 GenerateXml()而不是 XmlDoc.SaveToStream(),那么你需要重新考虑你的服务器的设计,并想出一个办法来加快 GenerateXml(),或刚刚摆脱了 IXMLDocument 并手动创建XML,所以你可以用它发送的 ResponseInfo.Connection.IOHandler 为您所创建的XML内容。

If, on the other hand, the bulk of your waiting is inside of GenerateXml() and not in XmlDoc.SaveToStream(), then you need to rethink your server design, and figure out a way to speed up GenerateXml(), or just get rid of IXMLDocument and create the XML manually so you can send it using the ResponseInfo.Connection.IOHandler as you are creating the XML content.