关于java发起http请求

我们到底能走多远系列(41)

扯淡:

  好久没总结点东西了,技术上没什么总结,感觉做事空牢牢的。最近也比较疲惫。

  分享些东西,造福全人类~

主题:

1,java模拟发起一个http请求

使用HttpURLConnection,可以通过setRequestProperty方法来设置http header的内容。
/**
      * post请求
      * @param strUrl
      * @param content
      * @param charset
      * @return
      */
     public static String sendPost(String strUrl, String content, String charset)  {
           URL httpurl = null;
           HttpURLConnection httpConn = null;
           String returnStr = "";
           PrintWriter outs = null;
            try {
                httpurl = new URL(strUrl);
                httpConn = (HttpURLConnection) httpurl.openConnection();
                httpConn.setRequestMethod( "POST"); // 默认是post
                 // 设置是否向httpUrlConnection输出,因为这个是post请求,参数要放在 http正文内,因此需要设为true, 默认情况下是false;  
                httpConn.setDoOutput( true);
                 // 设置是否从httpUrlConnection读入,默认情况下是true;
                httpConn.setDoInput( true);
                httpConn.setRequestProperty( "Content-Type", "text/xml");
                outs = new PrintWriter(httpConn.getOutputStream());
                outs.print(content);
                outs.flush();
                outs.close();
                 // 字节流 读取全部内容 包括换行符
                returnStr = inputStreamToString(httpConn.getInputStream(), charset);
           } catch (Exception e) {
                 log.error( "执行HTTP Post请求" + strUrl + "时,发生异常!" , e);
                 if(outs != null){
                     outs.close();
                     outs = null;
                }
                 return returnStr;
           } finally {
                 if (httpConn != null)
                     httpConn.disconnect();
                 if(outs != null){
                     outs.close();
                     outs = null;
                }
           }
            return returnStr;
     }

     public static String inputStreamToString(InputStream in,String encoding) throws Exception{ 
        ByteArrayOutputStream outStream = new ByteArrayOutputStream(); 
        byte[] data = new byte[ BUFFER_SIZE]; 
        int count = -1; 
        while((count = in.read(data,0, BUFFER_SIZE)) != -1)
            outStream.write(data, 0, count);
        in.close();
        data = null; 
        return new String(outStream.toByteArray(),encoding); 
    } 
 
在下面的代码中,我们单独分了一个inputStreamToString方法来读取请求响应的内容。
这里使用了ByteArrayOutputStream来读取,可以读取response的body的完整内容,不会丢失换行,这个要注意一下。比如我们使用BufferedReader,代码会类似于这样:
            String line, result = "";
            BufferedReader in = new BufferedReader( new InputStreamReader(conn.getInputStream(), "utf-8" ));
            while ((line = in. readLine()) != null) {
                result += line + "\n";
            }
            in.close();

 

为了不丢掉换行,需要自己拼接"\n",还有这地方还有个问题,那就是在循环中使用“+”来拼接字符串的问题,性能不行。参考:参考
 
也可以使用HttpClient 来发送post请求,感受下:
public static String sendPost(String url, Map<String, String> params,
               String charset) {
          StringBuffer response = new StringBuffer();
          HttpClient client = new HttpClient();
          HttpMethod method = new PostMethod(url);
        method.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES);
        method.setRequestHeader("Cookie", "special-cookie=value");
          method.setRequestHeader("", "");
          // 设置Http Post数据
          if (params != null) {
               HttpMethodParams p = new HttpMethodParams();
               for (Map.Entry<String, String> entry : params.entrySet()) {
                    p.setParameter(entry.getKey(), entry.getValue());
               }
               method.setParams(p);
          }
          try {
               client.executeMethod(method);
               if (method.getStatusCode() == HttpStatus.SC_OK) {
                    BufferedReader reader = new BufferedReader(
                              new InputStreamReader(method.getResponseBodyAsStream(),
                                        charset));
                    String line;
                    while ((line = reader.readLine()) != null) {
                         response.append(line);
                    }
                    reader.close();
               }
          } catch (IOException e) {
               log.error("执行HTTP Post请求" + url + "时,发生异常!", e);
          } finally {
               method.releaseConnection();
          }
          return response.toString();
     }

 

2,模拟登录微信公众平台

  既然我们可以模拟post请求,那么理论上我们也可以模拟登录,一般来说登录的过程就是发起一个post请求,把账号密码发送给服务器,服务器验证通过后,在resopnse里写cookies,然后跳转页面,接下来和服务器的交互就要基于cookies了。
那么我们要做的就是发起一个请求,然后把成功返回的cookies保存起来,每次后续需要登录状态才能和服务器交互的操作都带上cookies 。关于cookies
 
  其实就是这么个过程:
    client ---post请求---> server 
    client <--返回cookies--server
    client -请求带cookies--server
  首先,登录的请求是这样的:
  
  我们还可以注意到header中有一个Referer, 也是很有意思的,它的意思是这个请求来自于哪里。这个可以做统计啊,限制啊什么的。虽然我们可以模拟,哈哈。但是很多正规的网站都会用这个东西。有兴趣可以了解下~

  代码类似如下:

public final static String REFERER_H = "Referer";

private LoginResult _login(String username, String pwd) {
          LoginResult loginResult = new LoginResult();
          try {
               PostMethod post = new PostMethod(LOGIN_URL);
               post.setRequestHeader("Referer", "https://mp.weixin.qq.com/");
               post.setRequestHeader(USER_AGENT_H, USER_AGENT);
               NameValuePair[] params = new NameValuePair[] {
                         new NameValuePair("username", username),
                         new NameValuePair("pwd", DigestUtils.md5Hex(pwd
                                   .getBytes())), new NameValuePair("f", "json"),
                         new NameValuePair("imagecode", "") };
               post.setQueryString(params);
               int status = client.executeMethod(post);
               if (status == HttpStatus.SC_OK) {
                    String ret = post.getResponseBodyAsString();
                    LoginJson retcode = JSON.parseObject(ret, LoginJson.class);
                    // System.out.println(retcode.getRet());
                    if ((retcode.getBase_resp().getRet() == 302 || retcode
                              .getBase_resp().getRet() == 0)) {
                         loginResult.setCookie(client.getState().getCookies());
                         StringBuffer cookie = new StringBuffer();
                         // 处理cookies
                         for (Cookie c : client.getState().getCookies()) {
                              cookie.append(c.getName()).append("=")
                                        .append(c.getValue()).append(";");
                              cookiemap.put(c.getName(), c.getValue());
                         }
                         loginResult.setCookiestr(cookie.toString());
                         loginResult.setToken(getToken(retcode.getRedirect_url()));
                         loginResult.setLogin(true);
                         return loginResult;
                    }else{
                         loginResult.setLogin(false);
                         return loginResult;
                    }
               }
          } catch (Exception e) {
               String info = "【登录失败】【发生异常:" + e.getMessage() + "】";
               System.err.println(info);
               log.debug(info);
               log.info(info);
               return loginResult;
          }
          return loginResult;
     }

 

  全部代码已经共享出来了github  希望能帮到你~

 

3,重定向问题

  面试中是不是曾经也被问到过:forword和redirect的区别。

  网上的回答也有很多了,回过头来再看看这个问题。其实forword,可以说是服务器发起一个请求,访问自身应用下的资源。因为是服务器自己发起的,所以先前由客户端传递过来的参数就可以保持住了,而且是把访问的这个内部资源返回的内容作为客户端最初请求的响应,所以浏览器的url不会变。

  那么redirect的原理:其实是服务端在接到客户端请求后,在响应中的header中加上了一个location的东西,这个东西的原理又是:

  当浏览器接受到头信息中的 Location: xxxx 后,就会自动跳转到 xxxx 指向的URL地址,这点有点类似用 js 写跳转。但是这个跳转只有浏览器知道,不管体内容里有没有东西,用户都看不到。例:header("Location: http://www.xker.com/");也就是说redirect他的实现也依赖了浏览器的机制配合的。
  那么这样就明白了,先回一个带有location的响应,然后浏览器自己发起一个新的向location下的值的请求。因为是浏览器自己发起的请求,所以第一次发起的请求中的参数就不能保持,而因为是一个新的请求,所以浏览器的url会改变。
  还可以知道,在带有location的响应中,HTTP STATUS CODE 还是302。
  这也许是更深一点的理解吧~
 
 

  

 

让我们继续前行

----------------------------------------------------------------------

努力不一定成功,但不努力肯定不会成功。

posted on 2014-07-03 22:15  每当变幻时  阅读(9821)  评论(0编辑  收藏  举报

导航