Package iqq.im.service

Source Code of iqq.im.service.ApacheHttpService$QQHttpPostRequestProducer

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Project  : WebQQCore
* Package  : iqq.im.service
* File     : ApacheHttpService.java
* Author   : solosky < solosky772@qq.com >
* Created  : 2013-2-27
* License  : Apache License 2.0
*/
package iqq.im.service;

import iqq.im.QQException;
import iqq.im.QQException.QQErrorCode;
import iqq.im.action.AbstractHttpAction;
import iqq.im.core.QQConstants;
import iqq.im.core.QQContext;
import iqq.im.http.QQHttpCookie;
import iqq.im.http.QQHttpCookieJar;
import iqq.im.http.QQHttpListener;
import iqq.im.http.QQHttpRequest;
import iqq.im.http.QQHttpResponse;
import iqq.im.http.QQSSLSocketFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.FileNameMap;
import java.net.URI;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.net.ssl.SSLContext;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.ProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.mime.FormBodyPart;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.nio.client.DefaultHttpAsyncClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.nio.ContentEncoder;
import org.apache.http.nio.IOControl;
import org.apache.http.nio.client.methods.AsyncByteConsumer;
import org.apache.http.nio.conn.scheme.AsyncScheme;
import org.apache.http.nio.conn.ssl.SSLLayeringStrategy;
import org.apache.http.nio.protocol.BasicAsyncRequestProducer;
import org.apache.http.nio.reactor.IOReactorException;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
*
* 使用 AsyncHttpClient实现的Http服务
* http://hc.apache.org/httpcomponents-asyncclient-dev/index.html
*
* @author solosky
*/
public class ApacheHttpService extends AbstractService implements HttpService{
  private static final Logger LOG = LoggerFactory.getLogger(ApacheHttpService.class);
  private DefaultHttpAsyncClient asyncHttpClient;
  private QQHttpCookieJar cookieJar;
  private String userAgent;
  /** {@inheritDoc} */
  @Override
  public void setHttpProxy(ProxyType proxyType, String proxyHost,
      int proxyPort, String proxyAuthUser, String proxyAuthPassword) {
    // TODO ...
  }

  /** {@inheritDoc} */
  @Override
  public void setUserAgent(String userAgent) {
    this.userAgent = userAgent;
  }

  /** {@inheritDoc} */
  @Override
  public QQHttpRequest createHttpRequest(String method, String url) {
    QQHttpRequest req = new QQHttpRequest(url, method);
    req.addHeader("User-Agent", userAgent != null ? userAgent : QQConstants.USER_AGENT);
    req.addHeader("Referer", QQConstants.REFFER);
    return req;
  }

  /** {@inheritDoc} */
  @Override
  public Future<QQHttpResponse> executeHttpRequest(QQHttpRequest request, QQHttpListener listener)
    throws QQException {
    try {
      URI uri = URI.create(request.getUrl());
     
      if (request.getMethod().equals("POST")) {
        HttpPost httppost = new HttpPost(uri);
        HttpHost httphost = URIUtils.extractHost(uri);
        if(httphost == null) {
          LOG.error("host is null, url: " + uri.toString());
          httphost  = new HttpHost(uri.getHost());
        }
       
        if(request.getReadTimeout() > 0){
          HttpConnectionParams.setSoTimeout(httppost.getParams(), request.getReadTimeout());
        }
        if(request.getConnectTimeout() > 0){
          HttpConnectionParams.setConnectionTimeout(httppost.getParams(),request.getConnectTimeout());
        }
       
        if (request.getFileMap().size() > 0) {
          MultipartEntity entity = new MultipartEntity();
          String charset = request.getCharset();
         
          Map<String, String> postMap = request.getPostMap();
          for (String key : postMap.keySet()) {
            String value = postMap.get(key);
            value = value==null ? "" : value;
            entity.addPart(key, new StringBody(value, Charset.forName(charset)));
          }
         
          Map<String, File> fileMap = request.getFileMap();
          for (String key : fileMap.keySet()) {
            File value = fileMap.get(key);
            entity.addPart(new FormBodyPart(key, new FileBody(value, getMimeType(value))));
          }
          httppost.setEntity(entity);
        } else if(request.getPostMap().size() > 0) {
          List<NameValuePair>list=new ArrayList<NameValuePair>();
         
          Map<String, String> postMap = request.getPostMap();
          for (String key : postMap.keySet()) {
            String value = postMap.get(key);
            value = value==null ? "" : value;
            list.add(new BasicNameValuePair(key, value));
          }
          httppost.setEntity(new UrlEncodedFormEntity(list, request.getCharset()));
        }
        Map<String, String> headerMap = request.getHeaderMap();
        for(String key: headerMap.keySet()){
          httppost.addHeader(key, headerMap.get(key));
        }
        QQHttpPostRequestProducer producer = new QQHttpPostRequestProducer(httphost, httppost, listener);
        QQHttpResponseConsumer  consumer = new QQHttpResponseConsumer(request,listener, cookieJar);
        QQHttpResponseCallback callback = new QQHttpResponseCallback(listener);
        Future<QQHttpResponse> future = asyncHttpClient.execute( producer, consumer, callback);
        return new ProxyFuture(future, consumer, producer);
       
      }else if(request.getMethod().equals("GET")){
        HttpGet httpget = new HttpGet(uri);
        HttpHost httphost = URIUtils.extractHost(uri);
        if(httphost == null) {
          LOG.error("host is null, url: " + uri.toString());
          httphost  = new HttpHost(uri.getHost());
        }
        Map<String, String> headerMap = request.getHeaderMap();
        for(String key: headerMap.keySet()){
          httpget.addHeader(key, headerMap.get(key));
        }
        if(request.getReadTimeout() > 0){
          HttpConnectionParams.setSoTimeout(httpget.getParams(), request.getReadTimeout());
        }
        if(request.getConnectTimeout() > 0){
          HttpConnectionParams.setConnectionTimeout(httpget.getParams(),request.getConnectTimeout());
        }
       
        return asyncHttpClient.execute(new QQHttpGetRequestProducer(httphost, httpget),
            new QQHttpResponseConsumer(request, listener, cookieJar),
            new QQHttpResponseCallback(listener));
       
      }else{
        throw new QQException(QQErrorCode.IO_ERROR, "not support http method:" + request.getMethod());
      }
    } catch (IOException e) {
      throw new QQException(QQErrorCode.IO_ERROR);
    }
  }

  /** {@inheritDoc} */
  @Override
  public QQHttpCookie getCookie(String name, String url) {
    return cookieJar.getCookie(name, url);
  }

  /** {@inheritDoc} */
  @Override
  public void init(QQContext context) throws QQException {
    super.init(context);
    try {
      SSLContext sslContext = new QQSSLSocketFactory().getSSLContext();
      SSLContext.setDefault(sslContext);
      asyncHttpClient = new DefaultHttpAsyncClient();
     
      HttpParams httpParams = asyncHttpClient.getParams();
          HttpConnectionParams.setSoTimeout(httpParams, QQConstants.HTTP_TIME_OUT);
          HttpConnectionParams.setConnectionTimeout(httpParams, QQConstants.HTTP_TIME_OUT);
          HttpConnectionParams.setTcpNoDelay(httpParams, true);
          HttpConnectionParams.setSocketBufferSize(httpParams, 4096);
          HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
     
          asyncHttpClient.getConnectionManager()
          .getSchemeRegistry().register(new AsyncScheme("https", 443, new SSLLayeringStrategy(sslContext)));
      asyncHttpClient.setRedirectStrategy(new QQDefaultRedirectStrategy());
      asyncHttpClient.start();
      cookieJar = new QQHttpCookieJar();
    } catch (IOReactorException e) {
      throw new QQException(QQErrorCode.INIT_ERROR, e);
    }
  }

  /** {@inheritDoc} */
  @Override
  public void destroy() throws QQException {
    super.destroy();
    try {
      asyncHttpClient.shutdown();
    } catch (InterruptedException e) {
      throw new QQException(QQErrorCode.UNKNOWN_ERROR, e);
    }
  }
 
  private String getMimeType(File file){
     FileNameMap fileNameMap = URLConnection.getFileNameMap()
       return fileNameMap.getContentTypeFor(file.toString())
  }
 
 
  ////////////////////////////////////////////////////////////////////////
  private static final String CANCEL_EX_STRING = "http canceled by user!!!";
  /**
   * <p>checkCanceled.</p>
   *
   * @param isCanceled a boolean.
   * @throws java.io.IOException if any.
   */
  public static void checkCanceled(boolean isCanceled) throws IOException{
    if(isCanceled){
      throw new IOException(CANCEL_EX_STRING);
    }
  }
 
  static class QQDefaultRedirectStrategy extends DefaultRedirectStrategy{
    @Override
    protected URI createLocationURI(String url) throws ProtocolException {
      //腾讯的某些URL含有 {} ,URI解析会报错,在这之前替换下
      url = url.replaceAll("\\{", "%7b");
      url = url.replaceAll("\\}", "%7d");
      return super.createLocationURI(url);
    }
   
  }
 
    static class QQHttpResponseConsumer extends AsyncByteConsumer<QQHttpResponse> {
      private QQHttpListener httpListener;
      private QQHttpResponse httpResponse;
      private QQHttpCookieJar httpCookieJar;
      private OutputStream   httpOutStream;
      private long        readLength;
      private long        contentLength;
      private volatile boolean isCanceled;
     
        public QQHttpResponseConsumer(QQHttpRequest httpRequest, QQHttpListener httpListener, QQHttpCookieJar cookieJar) {
      this.httpListener = httpListener;
      this.readLength   = 0;
      this.contentLength = 0;
      this.httpResponse = new QQHttpResponse();
      this.httpCookieJar = cookieJar;
      this.isCanceled = false;
      if(httpRequest.getOutputStream() != null){
        httpOutStream = httpRequest.getOutputStream();
      }else{
        httpOutStream = new ByteArrayOutputStream();
      }
    }

    @Override
        protected void onResponseReceived(final HttpResponse response) {
      httpResponse.setResponseCode(response.getStatusLine().getStatusCode());
      httpResponse.setResponseMessage(response.getStatusLine().getReasonPhrase());
     
          Map<String, List<String>> fields = new HashMap<String, List<String>>();
      for(Header header: response.getAllHeaders()){
        List<String> values = fields.get(header.getName());
        if(values == null){
          values = new ArrayList<String>();
          fields.put(header.getName(), values);
        }
        values.add(header.getValue());
      }
      httpResponse.setHeaderFields(fields);
      contentLength = httpResponse.getContentLength();
      readLength = 0;
     
      List<String> setCookies = fields.get("Set-Cookie");
      if(setCookies != null){
        httpCookieJar.updateCookies(setCookies);
      }
     
      if(httpListener != null){
        httpListener.onHttpHeader(httpResponse);
        httpListener.onHttpRead(readLength, contentLength);
      }
        }

        @Override
        protected void releaseResources() {
        }

        @Override
        protected QQHttpResponse buildResult(final HttpContext context) {
          if(httpOutStream instanceof ByteArrayOutputStream){
        ByteArrayOutputStream out = (ByteArrayOutputStream) httpOutStream;
        httpResponse.setResponseData(out.toByteArray());
        try {
          httpOutStream.close();
        } catch (IOException e) {
          //ingore
        }
      }
      if(httpListener != null){
        httpListener.onHttpFinish(httpResponse);
      }
            return httpResponse;
        }

    @Override
    protected void onByteReceived(ByteBuffer buffer, IOControl control)
        throws IOException {
      checkCanceled(isCanceled);
     
      byte[] tmp = new byte[buffer.remaining()];
      buffer.get(tmp);
      httpOutStream.write(tmp);
      readLength += tmp.length;
      if(httpListener != null){
        httpListener.onHttpRead(readLength, contentLength);
      }
     
      checkCanceled(isCanceled);
    }
   
    public void cancelIt(){
      isCanceled = true;
    }

    }
   
  static class QQHttpPostRequestProducer extends BasicAsyncRequestProducer {
    private InputStream httpInStream;
    private long contentLength;
    private long writeLength;
    private QQHttpListener httpListener;
    private volatile boolean isCanceled;
   
    public QQHttpPostRequestProducer(HttpHost target,
        HttpEntityEnclosingRequest request, QQHttpListener listener)
        throws IOException {
      super(target, request);
      HttpEntity entity = request.getEntity();
      // TODO 暂时把所有的请求先读入内存,在存在大文件的时候可能OutOfMemory,以后重写一个基于MultiPartInputStream来优化
      ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
      entity.writeTo(byteOutStream);
      httpInStream = new ByteArrayInputStream(
          byteOutStream.toByteArray());

      byteOutStream.close();
      contentLength = entity.getContentLength();
      writeLength = 0;
      httpListener = listener;
     
      isCanceled = false;
     
      if(httpListener != null){
        httpListener.onHttpWrite(writeLength, contentLength);
      }
    }

    @Override
    public synchronized void produceContent(ContentEncoder encoder,
        IOControl ioctrl) throws IOException {
      checkCanceled(isCanceled);
     
      byte[] tmp = new byte[4096];
      int len = httpInStream.read(tmp);
      ByteBuffer buffer = ByteBuffer.wrap(tmp, 0, len);
      encoder.write(buffer);
      writeLength += len;
     
      if(httpListener != null){
        httpListener.onHttpWrite(writeLength, contentLength);
      }
      checkCanceled(isCanceled);
    }
   
    public void cancelIt(){
      isCanceled = true;
    }
  }
 
    static class QQHttpGetRequestProducer extends BasicAsyncRequestProducer {
      public QQHttpGetRequestProducer(final HttpHost target, final HttpRequest request) {
          super(target, request);
      }
    }
 
  static class QQHttpResponseCallback implements FutureCallback<QQHttpResponse>{
    private QQHttpListener httpListener;
    public QQHttpResponseCallback(QQHttpListener httpListener) {
      this.httpListener = httpListener;
    }

    @Override
    public void cancelled() {
    }

    @Override
    public void completed(QQHttpResponse response) {
    }

    @Override
    public void failed(Exception ex) {
      if(ex instanceof IOException
          && CANCEL_EX_STRING.equals(ex.getMessage())){
        return;
      }
      if( httpListener != null){
        httpListener.onHttpError(ex);
      }
    }
  }
 
  static class ProxyFuture implements Future<QQHttpResponse>{
    private Future<QQHttpResponse> proxy;
    private QQHttpResponseConsumer consumer;
    private QQHttpPostRequestProducer producer;
   
    public ProxyFuture(Future<QQHttpResponse> proxy,
        QQHttpResponseConsumer consumer,
        QQHttpPostRequestProducer producer) {
      this.proxy = proxy;
      this.consumer = consumer;
      this.producer = producer;
    }

    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
      consumer.cancel();
      try {
        producer.close();
      } catch (IOException e) {
        //Ignore
      }
      consumer.cancelIt();
      producer.cancelIt();
      return proxy.cancel(mayInterruptIfRunning);
    }

    @Override
    public QQHttpResponse get() throws InterruptedException, ExecutionException {
      return proxy.get();
    }

    @Override
    public QQHttpResponse get(long timeout, TimeUnit unit) throws InterruptedException,
        ExecutionException, TimeoutException {
      return proxy.get();
    }

    @Override
    public boolean isCancelled() {
      return proxy.isCancelled();
    }

    @Override
    public boolean isDone() {
      return proxy.isDone();
    }
   
  }

}
TOP

Related Classes of iqq.im.service.ApacheHttpService$QQHttpPostRequestProducer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.