How to parse a none standard HTTP response?

北慕城南 提交于 2019-12-05 01:24:48

After a lot of research for a small/lite http client library, I ran into this port of the apache httpclient for android. The library provided a complete support for http connections. Then I simply modified the source code, particularly the BasicLineParser to replace ICY with HTTP/1.0.

I had similar problem with KitKat and had a success with using two classes found here for http post. They are incredibly easy to use and you can modify the protocol params easily too.

There is another solution to this issue in Android 4.4 but it requires using Apache HttpClient. This is based on possibility of providing custom response parser into Apache Http engine that can change ICY 200 OK to HTTP/1.0 200 OK. This is based on general idea presented in:

http://hc.apache.org/httpcomponents-client-4.2.x/tutorial/html/advanced.html

I have successfully used following code.

public class IcyClientConnection extends DefaultClientConnection{

@Override
protected HttpMessageParser createResponseParser(SessionInputBuffer buffer,
        HttpResponseFactory responseFactory, HttpParams params) {

    return new IcyHttpResponseParser(
            buffer, 
            new BasicLineParser (), 
            responseFactory, 
            params);

}

}


public class IcyClientConnectionOperator extends DefaultClientConnectionOperator {

    public IcyClientConnectionOperator(SchemeRegistry schemes) {
        super(schemes);
    }

    @Override
    public OperatedClientConnection createConnection() {

        return new IcyClientConnection();
    }

}



public class IcyClientConnManager extends SingleClientConnManager  {


    public IcyClientConnManager(HttpParams params, SchemeRegistry schreg) {
        super(params, schreg);
    }

    @Override
    protected ClientConnectionOperator createConnectionOperator(
            SchemeRegistry schreg) {
        return new IcyClientConnectionOperator(schreg);
    }

}

Now you have to extend parser used by default and add code that will change wrong server replay to correct one. Normally code will block on hasProtocolVersion.

public class IcyHttpResponseParser extends DefaultResponseParser{

    private CharArrayBuffer icyLineBuf;

    private int icyMaxGarbageLines = 1000;
    private final HttpResponseFactory icyResponseFactory;




public IcyHttpResponseParser(SessionInputBuffer buffer, LineParser parser,
        HttpResponseFactory responseFactory, HttpParams params) {
    super(buffer, parser, responseFactory, params);

    this.icyLineBuf = new CharArrayBuffer(128);
    icyResponseFactory = responseFactory;
}

@Override
protected HttpMessage parseHead(SessionInputBuffer sessionBuffer)
        throws IOException, HttpException {
    int count = 0;
    ParserCursor cursor = null;
    do {
        // clear the buffer
        this.icyLineBuf.clear();
        final int i = sessionBuffer.readLine(this.icyLineBuf);

        //look for ICY and change to HTTP to provide compatibility with non standard shoutcast servers

        String tmp = icyLineBuf.substring(0, this.icyLineBuf.length());
        if(tmp.contains("ICY ")){
            tmp = tmp.replace("ICY", "HTTP/1.0");
        }
        //copy
        this.icyLineBuf = new CharArrayBuffer(128);
        System.arraycopy(tmp.toCharArray(), 0, icyLineBuf.buffer(), 0, tmp.length());
        icyLineBuf.setLength( tmp.length());


        if (i == -1 && count == 0) {
            // The server just dropped connection on us
            throw new NoHttpResponseException("The target server failed to respond");
        }
        cursor = new ParserCursor(0, this.icyLineBuf.length());
        if (lineParser.hasProtocolVersion(this.icyLineBuf, cursor)) {
            // Got one
            break;
        } else if (i == -1 || count >= this.icyMaxGarbageLines) {
            // Giving up
            throw new ProtocolException("The server failed to respond with a " +
                    "valid HTTP response");
        }
        //if (this.log.isDebugEnabled()) {
        //   this.log.debug("Garbage in response: " + this.lineBuf.toString());
       // }
        count++;
    } while(true);
    //create the status line from the status string
    final StatusLine statusline = lineParser.parseStatusLine(this.icyLineBuf, cursor);
    return this.icyResponseFactory.newHttpResponse(statusline, null);
}
}

Plug in HttpClient:

Scheme http = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
Scheme ftp = new Scheme("ftp", PlainSocketFactory.getSocketFactory(), 21);

SchemeRegistry sr = new SchemeRegistry();
sr.register(http);
sr.register(ftp);

HttpClient httpClient = new DefaultHttpClient(new IcyClientConnManager(params, sr), params);

This is still being tested but initial results are promising.

Thanks @Michael M, you can even make it simpler by subclassing the BasicLineParser instead of subclassing the DefaultResponseParser.

I've uploaded the code into a gist

To use it:

IcyGetRequest request = new IcyGetRequest(urlStr);
HttpResponse response = request.get();
int responseCode = response.getStatusLine().getStatusCode();

Create an runnable that creates socket proxy then you will be able to response with HTTP/1.0 instead of ICY , then just connect to this local socket proxy with your player

Draško Kokić

Here a modification of the solution from Michal M in case you don't like to create lots of subclasses just to configure already available HttpClient classes.

final SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
HttpClient httpClient = new DefaultHttpClient() {
    @Override
    protected ClientConnectionManager createClientConnectionManager() {
        return new SingleClientConnManager(getParams(), schemeRegistry) {
            @Override
            protected ClientConnectionOperator createConnectionOperator(SchemeRegistry schreg) {
                return new DefaultClientConnectionOperator(schreg) {
                    @Override
                    public OperatedClientConnection createConnection() {
                        return new DefaultClientConnection() {
                            @Override
                            protected HttpMessageParser createResponseParser(SessionInputBuffer buffer, HttpResponseFactory responseFactory, HttpParams params) {
                                return new IcyHttpResponseParser(buffer, new BasicLineParser(), responseFactory, params);
                            }
                        };
                    }
                };
            }
        };
    }
};

Probably there is a way to get the SchemeRegistry obsoleted if one could get hold somehow from within the DefaultHttpClient class.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!