Groovy RestClient 流自动关闭的问题

Groovy RestClient 自动关闭流的问题

问题描述

groovy 中使用http-builder-0.72.jar读取图片字节流,遇到 Attempted read from closed stream异常。

问题代码

import groovyx.net.http.HTTPBuilder
import groovyx.net.http.Method
import groovyx.net.http.RESTClient
import sun.misc.BASE64Encoder

import static groovyx.net.http.ContentType.TEXT
import static groovyx.net.http.ContentType.ANY
import static groovyx.net.http.ContentType.HTML
def restclient = new RESTClient("http://www.xxxxx.com")

def params = [params: [frameNo  : 'L65654748903789765',
                       engineNo : '458237',
                       license  : '新车',
                       agentCode: '03515500']]

restclient.get(params) {
    rsp,reader ->
        rsp.entity.content.bytes
}

源码分析

RestClient是对HttpBuilder为满足festful调用风格的一层封装。

    public Object get( Map<String,?> args ) throws ClientProtocolException,
            IOException, URISyntaxException {
        return doRequest( new RequestConfigDelegate( args, new HttpGet(), null ) );
    }

实际调用HttpBuilder的doRequest方法

       protected Object doRequest( URI uri, Method method, Object contentType, Closure configClosure )
            throws ClientProtocolException, IOException {

        HttpRequestBase reqMethod;
        try { reqMethod = method.getRequestType().newInstance();
        // this exception should reasonably never occur:
        } catch ( Exception e ) { throw new RuntimeException( e ); }

        reqMethod.setURI( uri );
        RequestConfigDelegate delegate = new RequestConfigDelegate( reqMethod, contentType,
                this.defaultRequestHeaders,
                this.defaultResponseHandlers );
        configClosure.setDelegate( delegate );
        configClosure.setResolveStrategy( Closure.DELEGATE_FIRST );
        configClosure.call( reqMethod );

        return this.doRequest( delegate );
    }

    /**
     * All <code>request</code> methods delegate to this method.
     */
    protected Object doRequest( final RequestConfigDelegate delegate )
            throws ClientProtocolException, IOException {
        delegate.encodeBody();
        final HttpRequestBase reqMethod = delegate.getRequest();

        final Object contentType = delegate.getContentType();

        if ( this.autoAcceptHeader ) {
            String acceptContentTypes = contentType.toString();
            if ( contentType instanceof ContentType )
                acceptContentTypes = ((ContentType)contentType).getAcceptHeader();
            reqMethod.setHeader( "Accept", acceptContentTypes );
        }

        reqMethod.setURI( delegate.getUri().toURI() );
        if ( reqMethod.getURI() == null)
            throw new IllegalStateException( "Request URI cannot be null" );

        log.debug( reqMethod.getMethod() + " " + reqMethod.getURI() );

        // set any request headers from the delegate
        Map<?,?> headers = delegate.getHeaders();
        for ( Object key : headers.keySet() ) {
            Object val = headers.get( key );
            if ( key == null ) continue;
            if ( val == null ) reqMethod.removeHeaders( key.toString() );
            else reqMethod.setHeader( key.toString(), val.toString() );
        }

        ResponseHandler<Object> responseHandler = new ResponseHandler<Object>() {
            public Object handleResponse(HttpResponse response)
                throws ClientProtocolException, IOException {
                HttpResponseDecorator resp = new HttpResponseDecorator(
                        response, delegate.getContext(), null );
                try {
                    int status = resp.getStatusLine().getStatusCode();
                    Closure responseClosure = delegate.findResponseHandler( status );
                    log.debug( "Response code: " + status + "; found handler: " + responseClosure );

                    Object[] closureArgs = null;
                    switch ( responseClosure.getMaximumNumberOfParameters() ) {
                    case 1 :
                        closureArgs = new Object[] { resp };
                        break;
                    case 2 : // parse the response entity if the response handler expects it:
                        HttpEntity entity = resp.getEntity();
                        try {
                            if ( entity == null || entity.getContentLength() == 0 )
                                closureArgs = new Object[] { resp, null };
                            else closureArgs = new Object[] { resp, parseResponse( resp, contentType ) };
                        }
                        catch ( Exception ex ) {
                            Header h = entity.getContentType();
                            String respContentType = h != null ? h.getValue() : null;
                            log.warn( "Error parsing '" + respContentType + "' response", ex );
                            throw new ResponseParseException( resp, ex );
                        }
                        break;
                    default:
                        throw new IllegalArgumentException(
                                "Response closure must accept one or two parameters" );
                    }

                    Object returnVal = responseClosure.call( closureArgs );
                    log.trace( "response handler result: " + returnVal );

                    return returnVal;
                }
                finally {
                    HttpEntity entity = resp.getEntity();
                    if ( entity != null ) entity.consumeContent();
                }
            }
        };
                return getClient().execute(reqMethod, responseHandler, delegate.getContext());
    }

留意其中responseHandler处理逻辑中对针对参数个数不通处理不同的返回结果的逻辑。

switch ( responseClosure.getMaximumNumberOfParameters() ) {
                    case 1 :
                        closureArgs = new Object[] { resp };
                        break;
                    case 2 : // parse the response entity if the response handler expects it:
                        HttpEntity entity = resp.getEntity();
                        try {
                            if ( entity == null || entity.getContentLength() == 0 )
                                closureArgs = new Object[] { resp, null };
                            else closureArgs = new Object[] { resp, parseResponse( resp, contentType ) };
                        }

当处理闭包只有一个参数的时候,会直接返回resp也就是一个被封装后的httpResonse对象,我们可以从中拿到尚未被读取的字节流inputStream,进行字节流处理。
而如果是两个参数时,即问题中的形式,则会根据返回的contentType不同调用 parseResponse( resp, contentType ),此函数中会根据返回contentType选择对应的解析器进行返回结果的处理,即我们常见的json,xml ,html, ParserRegistry类中会根据不同contentType执行各自的解析过程。而当解析html,json ,xml的时候是会从输入流中读取数据进行封装。即轮到我们的闭包执行的时候,inputstream中的数据已经被读取过了,固此时会报错 Attempted read from closed stream。

问题解决

有以上可知,有两种问题解决此问题
方法一,将结果处理闭包中返回参数设置为一个,此时不会进行数据解析的过程,可以拿到对应的inputstream然后进行处理

import groovyx.net.http.HTTPBuilder
import groovyx.net.http.Method
import groovyx.net.http.RESTClient
import sun.misc.BASE64Encoder

import static groovyx.net.http.ContentType.TEXT
import static groovyx.net.http.ContentType.ANY
import static groovyx.net.http.ContentType.HTML
def restclient = new RESTClient("http://www.xxxxx.com")

def params = [params: [frameNo  : 'L65654748903789765',
                       engineNo : '458237',
                       license  : '新车',
                       agentCode: '03515500']]

restclient.get(params) {
    rsp->
        rsp.entity.content.bytes
}

方法二 设置contentType 为TEXT
TEXT的parser过程不会读取流的字节,而只是直接返回一个封装后的InputStreamReader,所以此时依然可以拿到可读的inputStream,但是如果不设置contentType,则默认为ANY 会以返回报文头中的contentType来获取对应解析器。

import groovyx.net.http.HTTPBuilder
import groovyx.net.http.Method
import groovyx.net.http.RESTClient
import sun.misc.BASE64Encoder

import static groovyx.net.http.ContentType.TEXT
import static groovyx.net.http.ContentType.ANY
import static groovyx.net.http.ContentType.HTML
def restclient = new RESTClient("http://www.xxxxx.com",TEXT)

def params = [params: [frameNo  : 'L65654748903789765',
                       engineNo : '458237',
                       license  : '新车',
                       agentCode: '03515500']]

restclient.get(params) {
    rsp,reader->
        rsp.entity.content.bytes
}

其实如果只是简单的要拿一个url返回的字节流可用最简单的方式

new URL('http://www.baidu.com').bytes

扩展

关于RestClient,和HttpBuilder的使用,RestClient本身是对HttpBuilder的一层Rest封装,所以如果使用RestClient,只使用他本身的get,post等方法即可,而其参数都是固定一个Map,会使用默认responseHandler进行处理进而返回一个HttpResponseDecorator对象,而例子中我们使用的方法其实是HttpBuilder的get(param,closure)方法,没有透彻理解restclient类的拓展目的。
对于HttpBuilder中params,query ,queryString参数的处理过程,先解析params以 ?key=value&key1=value1的方式拼接到uri,然后解析queryString,而如果有此参数的话,会更新uri,直接变为uri?qryString的形式,query参数是和params的一个合并,有则更新,没有则添加。

  protected void setPropertiesFromMap( Map<String,?> args ) throws URISyntaxException {
            if ( args == null ) return;
            if ( args.containsKey( "url" ) ) throw new IllegalArgumentException(
                    "The 'url' parameter is deprecated; use 'uri' instead" );
            Object uri = args.remove( "uri" );
            if ( uri == null ) uri = defaultURI;
            if ( uri == null ) throw new IllegalStateException(
                    "Default URI is null, and no 'uri' parameter was given" );
            this.uri = new URIBuilder( convertToURI( uri ) );

            Map query = (Map)args.remove( "params" );
            if ( query != null ) {
                log.warn( "'params' argument is deprecated; use 'query' instead." );
                this.uri.setQuery( query );
            }
            String queryString = (String)args.remove("queryString");
            if ( queryString != null ) this.uri.setRawQuery(queryString);

            query = (Map)args.remove( "query" );
            if ( query != null ) this.uri.addQueryParams( query );
            Map headers = (Map)args.remove( "headers" );
            if ( headers != null ) this.getHeaders().putAll( headers );

            Object path = args.remove( "path" );
            if ( path != null ) this.uri.setPath( path.toString() );

            Object contentType = args.remove( "contentType" );
            if ( contentType != null ) this.setContentType( contentType );

            contentType = args.remove( "requestContentType" );
            if ( contentType != null ) this.setRequestContentType( contentType );

            Object body = args.remove("body");
            if ( body != null ) this.setBody( body );

            if ( args.size() > 0 ) {
                String invalidArgs = "";
                for ( String k : args.keySet() ) invalidArgs += k + ",";
                throw new IllegalArgumentException("Unexpected keyword args: " + invalidArgs);
            }
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值