HttpClient之URI

一、URL简介

标准的URL格式:

协议模式://主机名:可选的端口/资源路径?可选查询#可选的片段

即:sheme://authority:port/path?query#fragment

完整的URL格式:

协议模式://用户名:密码@主机名:可选的端口/资源路径?可选查询#可选的片段

即:Scheme://userid:password@authority:port/path?query#fragment

注:用户信息一般使用,主要作用是在URL中添加用户信息的写法,这样可以省去访问SVN时要求输入用户登录信息

URL各组成部分在Header消息头的位置如下图

注:fragment(信息片段):用于指定网络资源中的片段。

详细资料可参考:

(1)Things YouShould Know About Fragment URLs

http://blog.httpwatch.com/2011/03/01/6-things-you-should-know-about-fragment-urls/

(2)QQ密码修改的小问题回顾下URL Fragment

http://www.cnblogs.com/syf/archive/2013/04/02/2995903.html

 

二、URIURL区别

URI,是uniformresource identifier,统一资源标识符,用来唯一的标识一个资源。URI有绝对和相对之分,绝对的URI指以scheme(后面跟着冒号)开头的URI。例如http://www.cnn.com/articles/articles.html就是绝对的URI;相对的URI不是以scheme(后面跟着冒号)开始的URI,例如articles/articles.html就是相对的URI

 

URLuniformresource locator,统一资源定位器,它是一种具体的URI,即URL可以用来标识一个资源。URLURI的一个子集,指明了如何locate这个资源。也即是说URL是一种具体的URI,它不仅唯一标识资源,而且还提供了定位该资源的信息。需要注意URL必须提供足够的信息来定位,是绝对的,而通常说的relative URL,则是针对absolute URL来说的,本质上还是绝对的。

 

注:URI的研究出现就是为了弥补URL的一些缺点,例如当资源改变时URL也需要变化的问题等

 

详细资料可参考:

http://docs.oracle.com/javase/1.5.0/docs/api/java/net/URI.html

http://en.wikipedia.org/wiki/Uniform_Resource_Identifier

 

三、HttpClient中的URI

HttpClient支持的URL格式为(默认忽略不处理fragment)

Scheme://[userid:password@]authority:port/path?query

一些知识的整理

URI-reference = [absoluteURI | relativeURI ] [ "#" fragment ]

absoluteURI   = scheme ":"( hier_part | opaque_part )

relativeURI   = ( net_path |abs_path | rel_path ) [ "?" query ]

hier_part     = ( net_path |abs_path ) [ "?" query ]

net_path      ="//" authority [ abs_path ]

abs_path     ="/" path_segments

rel_path       = [path ] [ ";" params ] [ "?" query ]

authority     =server | reg_name

host          =hostname | IPv4address | IPv6reference

 

一些名词解释:

reg_name指服务器注册的域名,如www.baidu.com

hier_part是指需要有分隔符对不同等级的组件进行分割

opaque_part指不需要有分隔符对不同组件进行分割

 

例如:完整的URL地址

http://userName:password@www.testpage.com/otengyue/article/search?ie=utf8&oe=utf8

URI-reference: http://userName:password@www.testpage.com/otengyue/article/search?ie=utf8&oe=utf8

Path:                    /otengyue/article/search

Host:       www.testpage.com

Authority:     userName:password@www.testpage.com

abs_path:   /otengyue/article/search

net_path:   //userName:password@www.testpage.com/otengyue/article/search

 

更多资料可查阅

URL规范(RFC1738)http://tools.ietf.org/html/rfc1738

介绍RFC1738(中文)博文:http://blog.csdn.net/msgsnd/article/details/2172306

 

HttpClient中有关URL处理的类图如下:

 

其中URI类是对URL元数据的封装。URI中元数据的存储默认采用给定的编码类型编码后存储,若要改变需要实例化时设置不编码实例。

(1)URI中元数据的默认编码方式为"UTF-8",若要更改默认的charset类型可以采用下面两种方法

<1>永久更改。调用URI的静态函数setDefaultProtocolCharset(Stringcharset)(不推荐)

如下代码(放在所有代码前面)

	try {
	     URI.setDefaultProtocolCharset("gbk");
	} catch (DefaultCharsetChanged cc) {
	     // CASE 1: the exception could be ignored, when it is set by user
	     if (cc.getReasonCode() == DefaultCharsetChanged.PROTOCOL_CHARSET) {
	     // CASE 2: let user know the default protocol charset changed
	     } else {
	     // CASE 2: let user know the default document charset changed
	     }
	     }

<2>临时更改。在构造函数中charset作为参数,只能更改当前实例化类的编码类型。

 

(2)URI元数据的设置/获取

getRaw*/getEscaped*  获得原始的URI对应元数据

get*                 获得对应的已解码得对应元数据

 

setRaw*/setEscaped*  设置URI对应元数据值(参数未经过编码直接设置)

set*                 设置URI对应元数据值(参数会经过编码后再设置)

 

(3)URI实例化

	boolean escaped=false;
	//使用绝对路径和选择是否编码为false实例化,注不设置则默认采用是否编码为true实例化,如下面集中实例化
	URI uri=new URI("http://www.baidu.com/search/s?ie=utf8&oe=utf8&wd=HttpClient&tn=98010089_dg&ch=1",escaped);
	//使用绝对路径
	URI uri1=new URI("http://www.baidu.com/search/s?ie=utf8&oe=utf8&wd=HttpClient&tn=98010089_dg&ch=1");
	//实例化时设置URI编码类型
	URI URI2=new URI("http://www.baidu.com","gbk");
	//使用相对路径,注不能忘记以"/"开头
	URI uri3=new URI("/ms?ie=utf8&oe=utf8&wd=HttpClient&tn=98010089_dg&ch=1");
	//使用baseURI和RelativeURI
	//uri4.getURI():<a target=_blank href="http://www.baidu.com/ms?ie=utf8&oe=utf8&wd=HttpClient&tn=98010089_dg&ch=1">http://www.baidu.com/ms?ie=utf8&oe=utf8&wd=HttpClient&tn=98010089_dg&ch=1</a>
	URI uri4=new URI(uri1,uri3);


 

(4)具体协议的使用

      针对具体的协议(http),则使用对应的URI的子类。例如针对Http协议的HttpUrl类封装了一系列的方法简化uri的操作 (HttpUrl是采用自动编码方式存储元数据)。另外HttpUrl无法调用无参构造函数(类型为protected)。下图为HttpUrl的构造函数图

 

四、编码问题

HttpClient处理字符编码大致包括URLHeader、和请求/响应体三个部分。

URL编码标准遵循RFC1738标准,标准为US-ASCII编码,但其不支持双字节,在HttpClientURI中采用UTF-8为默认的编码格式。HeaderContent-Type字段可能会包含字符编码信息。例如字符的设置:Content-Type: text/html; charset=UTF-8

GET的请求参数在QueryString中,是URI的一部分。因此,对于GET请求,关注于请求参数的中文编码。

POST的请求参数在Body中,因此,对于POST请求,关注Body的编码问题。

 

解决方案:

(1)GET请求参数编码

<1>设置URI编码类型

查看URI类代码,不设置编码则默认为UTF-8

    /**
     * The charset of the protocol used by this URI instance.
     */
    protected String protocolCharset = null;


    /**
     * The default charset of the protocol.  RFC 2277, 2396
     */
    protected static String defaultProtocolCharset = "UTF-8";

 

需要设置编码时,在构造函数中设置编码类型

 public URI(char[] escaped, String charset) 
        throws URIException, NullPointerException {
        protocolCharset = charset;
        parseUriReference(new String(escaped), true);
    }

<2>设置QueryString编码类型

一般中文编码问题只存在请求参数QueryString中,因此只需对请求参数设置编码即可。

queryString =EncodingUtil.formUrlEncode(params, "UTF-8"); //NameValuePair[] params

queryString = URIUtil.decode (params, "UTF-8"); //String params

(2)POST请求参数编码

主要是在Post请求头的Header中添加Content-Type: text/html; charset=UTF-8,

HttpMethodParams类中设置编码的源码如下(如果不设置则默认为ISO-8859-1)

     /**
     * Sets the default charset to be used for writing content body,
     * when no charset explicitly specified.
     * @param charset The charset
     */
    public void setContentCharset(String charset) {
        setParameter(HTTP_CONTENT_CHARSET, charset);
    }
     /**
     * Returns the default charset to be used for writing content body, 
     * when no charset explicitly specified.
     * @return The charset
     */
    public String getContentCharset() {
        String charset = (String) getParameter(HTTP_CONTENT_CHARSET);
        if (charset == null) {
            LOG.warn("Default content charset not configured, using ISO-8859-1");
            charset = "ISO-8859-1";
        }
        return charset;
    }

 

添加方法如下:

<1>在POST请求中的Header中设置Content-Type

PostMethod method = new PostMethod();

method.addRequestHeader("Content-Type","text/html;charset=UTF-8");

<2>设置HttpClientParam的ContentCharset

HttpClient httpClient = new HttpClient();

HttpClientParam params =httpClient.getParams();

params.setContentCharset("UTF-8");

<3>设置HttpMethodParams的ContentCharse

PostMethod method = new PostMethod();

HttpMethodParams params = method.getParams();

params.setContentCharset("UTF-8");

(3) 请求/响应体中body编码

HttpMethodBase类源码中获得响应Body的源码如下

    /**
     * Returns the response body of the HTTP method, if any, as a {@link String}. 
     * If response body is not available or cannot be read, returns <tt>null</tt>
     * The string conversion on the data is done using the character encoding specified
     * in <tt>Content-Type</tt> header. Buffers the response and this method can be 
     * called several times yielding the same result each time.
     * 
     * Note: This will cause the entire response body to be buffered in memory. A
     * malicious server may easily exhaust all the VM memory. It is strongly
     * recommended, to use getResponseAsStream if the content length of the response
     * is unknown or resonably large.
     * 
     * @return The response body or <code>null</code>.
     * 
     * @throws IOException If an I/O (transport) problem occurs while obtaining the 
     * response body.
     */
    public String getResponseBodyAsString() throws IOException {
        byte[] rawdata = null;
        if (responseAvailable()) {
            rawdata = getResponseBody();
        }
        if (rawdata != null) {
            return EncodingUtil.getString(rawdata, getResponseCharSet());
        } else {
            return null;
        }
    }
    /**
     * Returns the character encoding of the request from the <tt>Content-Type</tt> header.
     * 
     * @return String The character set.
     */
    public String getRequestCharSet() {
        return getContentCharSet(getRequestHeader("Content-Type"));
    }

 

可见,getResponseCharSet方法Content-Type Header获取响应数据的charset。这要求Servlet必须正确设置responseContent-Type Header

 

另外

通过查看源码可以看到在获得Response后,HttpClient识别编码的顺序是:http头信息的charset,如果头信息中没有charset,则查找HttpClientParamscontentCharset,如果没有指定编码,则是ISO-8859-1。其源码可在HttpMethodBase中查看

   /**
     * Returns the character encoding of the request from the <tt>Content-Type</tt> header.
     * 
     * @return String The character set.
     */
    public String getRequestCharSet() {
        return getContentCharSet(getRequestHeader("Content-Type"));
    }
    /**
     * Returns the character set from the <tt>Content-Type</tt> header.
     * 
     * @param contentheader The content header.
     * @return String The character set.
     */
    protected String getContentCharSet(Header contentheader) {
        LOG.trace("enter getContentCharSet( Header contentheader )");
        String charset = null;
        if (contentheader != null) {
            HeaderElement values[] = contentheader.getElements();
            // I expect only one header element to be there
            // No more. no less
            if (values.length == 1) {
                NameValuePair param = values[0].getParameterByName("charset");
                if (param != null) {
                    // If I get anything "funny" 
                    // UnsupportedEncondingException will result
                    charset = param.getValue();
                }
            }
        }
        if (charset == null) {
            charset = getParams().getContentCharset();
            if (LOG.isDebugEnabled()) {
                LOG.debug("Default charset used: " + charset);
            }
        }
        return charset;
    }

 

因此,在解决响应头中没有Content-Type:text/html; charset=utf-8而引起乱码时,可以通过设置发送请求头时设置

httpClient.getParams().setContentCharset("gbk");

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值