blob: d58f30ef6d16675d777c126709b2e922867332a2 [file] [log] [blame]
/*
* 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.
*/
package com.taobao.weex.http;
import static com.taobao.weex.http.WXHttpUtil.KEY_USER_AGENT;
import android.net.Uri;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.taobao.weex.WXEnvironment;
import com.taobao.weex.WXSDKManager;
import com.taobao.weex.adapter.IWXHttpAdapter;
import com.taobao.weex.adapter.URIAdapter;
import com.taobao.weex.annotation.JSMethod;
import com.taobao.weex.bridge.JSCallback;
import com.taobao.weex.bridge.WXBridgeManager;
import com.taobao.weex.common.WXModule;
import com.taobao.weex.common.WXRequest;
import com.taobao.weex.common.WXResponse;
import com.taobao.weex.utils.WXLogUtils;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class WXStreamModule extends WXModule {
public static final String STATUS_TEXT = "statusText";
public static final String STATUS = "status";
final IWXHttpAdapter mAdapter;
static final Pattern CHARSET_PATTERN = Pattern.compile("charset=([a-z0-9-]+)");
public WXStreamModule(){
this(null);
}
public WXStreamModule(IWXHttpAdapter adapter){
mAdapter = adapter;
}
/**
* send HTTP request
*
* @param params {method:POST/GET/PUT/DELETE/HEAD/PATCH,url:http://xxx,header:{key:value},
* body:{key:value}}
* @param callback formate:handler(err, response)
*/
@Deprecated
@JSMethod(uiThread = false)
public void sendHttp(JSONObject paramsObj, final String callback) {
String method = paramsObj.getString("method");
String url = paramsObj.getString("url");
JSONObject headers = paramsObj.getJSONObject("header");
String body = paramsObj.getString("body");
int timeout = paramsObj.getIntValue("timeout");
if (method != null) method = method.toUpperCase();
Options.Builder builder = new Options.Builder()
.setMethod(!"GET".equals(method)
&&!"POST".equals(method)
&&!"PUT".equals(method)
&&!"DELETE".equals(method)
&&!"HEAD".equals(method)
&&!"PATCH".equals(method)?"GET":method)
.setUrl(url)
.setBody(body)
.setTimeout(timeout);
extractHeaders(headers,builder);
sendRequest(builder.createOptions(), new ResponseCallback() {
@Override
public void onResponse(WXResponse response, Map<String, String> headers) {
if(callback != null && mWXSDKInstance != null)
WXBridgeManager.getInstance().callback(mWXSDKInstance.getInstanceId(), callback,
(response == null || response.originalData == null) ? "{}" :
readAsString(response.originalData,
headers!=null?getHeader(headers,"Content-Type"):""
));
}
}, null, mWXSDKInstance.getInstanceId(), mWXSDKInstance.getBundleUrl());
}
/**
*
* @param optionsStr request options include:
* method: GET 、POST、PUT、DELETE、HEAD、PATCH
* headers:object,request header
* url:
* body: "Any body that you want to add to your request"
* type: json、text、jsonp(json)
* @param callback finished callback,response object:
* status:status code
* ok:boolean is success,http status200~299
* statusText: statusText
* data: option type is json,data is object,not data is string
* headers: headers
*
* @param progressCallback in progress callback,for download progress and request state,response object:
* readyState: number connection status 1 OPENED 2 HEADERS_RECEIVED 3 LOADING
* status:status code
* length:headers Content-Length
* statusText:statusText
* headers: headers
*/
@JSMethod(uiThread = false)
public void fetch(JSONObject optionsObj , final JSCallback callback, JSCallback progressCallback){
fetch(optionsObj, callback, progressCallback, mWXSDKInstance.getInstanceId(), mWXSDKInstance.getBundleUrl());
}
public void fetch(JSONObject optionsObj , final JSCallback callback, JSCallback progressCallback, String instanceId, String bundleURL){
boolean invaildOption = optionsObj==null || optionsObj.getString("url")==null;
if(invaildOption){
if(callback != null) {
Map<String, Object> resp = new HashMap<>();
resp.put("ok", false);
resp.put(STATUS_TEXT, Status.ERR_INVALID_REQUEST);
callback.invoke(resp);
}
return;
}
String method = optionsObj.getString("method");
String url = optionsObj.getString("url");
JSONObject headers = optionsObj.getJSONObject("headers");
String body = optionsObj.getString("body");
String type = optionsObj.getString("type");
int timeout = optionsObj.getIntValue("timeout");
if (method != null) method = method.toUpperCase();
Options.Builder builder = new Options.Builder()
.setMethod(!"GET".equals(method)
&&!"POST".equals(method)
&&!"PUT".equals(method)
&&!"DELETE".equals(method)
&&!"HEAD".equals(method)
&&!"PATCH".equals(method)?"GET":method)
.setUrl(url)
.setBody(body)
.setType(type)
.setTimeout(timeout);
extractHeaders(headers,builder);
final Options options = builder.createOptions();
sendRequest(options, new ResponseCallback() {
@Override
public void onResponse(WXResponse response, Map<String, String> headers) {
if(callback != null) {
Map<String, Object> resp = new HashMap<>();
if(response == null|| "-1".equals(response.statusCode)){
resp.put(STATUS,-1);
resp.put(STATUS_TEXT,Status.ERR_CONNECT_FAILED);
}else {
int code = Integer.parseInt(response.statusCode);
resp.put(STATUS, code);
resp.put("ok", (code >= 200 && code <= 299));
if (response.originalData == null) {
resp.put("data", null);
} else {
String respData = readAsString(response.originalData,
headers != null ? getHeader(headers, "Content-Type") : ""
);
try {
resp.put("data", parseData(respData, options.getType()));
} catch (JSONException exception) {
WXLogUtils.e("", exception);
resp.put("ok", false);
resp.put("data","{'err':'Data parse failed!'}");
}
}
resp.put(STATUS_TEXT, Status.getStatusText(response.statusCode));
}
resp.put("headers", headers);
callback.invoke(resp);
}
}
}, progressCallback, instanceId, bundleURL);
}
Object parseData(String data, Options.Type type) throws JSONException{
if( type == Options.Type.json){
return JSONObject.parse(data);
}else if( type == Options.Type.jsonp){
if(data == null || data.isEmpty()) {
return new JSONObject();
}
int b = data.indexOf("(")+1;
int e = data.lastIndexOf(")");
if(b ==0 || b >= e || e <= 0){
return new JSONObject();
}
data = data.substring(b,e);
return JSONObject.parse(data);
}else {
return data;
}
}
static String getHeader(Map<String,String> headers,String key){
if(headers == null||key == null){
return null;
}
if(headers.containsKey(key)){
return headers.get(key);
}else{
return headers.get(key.toLowerCase());
}
}
static String readAsString(byte[] data,String cType){
String charset = "utf-8";
if(cType != null){
Matcher matcher = CHARSET_PATTERN.matcher(cType.toLowerCase());
if(matcher.find()){
charset = matcher.group(1);
}
}
try {
return new String(data,charset);
} catch (UnsupportedEncodingException e) {
WXLogUtils.e("", e);
return new String(data);
}
}
private void extractHeaders(JSONObject headers, Options.Builder builder){
//set user-agent
String UA = WXHttpUtil.assembleUserAgent(WXEnvironment.getApplication(),WXEnvironment.getConfig());
if(headers != null){
for (String key : headers.keySet()) {
if (key.equals(KEY_USER_AGENT)) {
UA = headers.getString(key);
continue;
}
builder.putHeader(key, headers.getString(key));
}
}
builder.putHeader(KEY_USER_AGENT,UA);
}
private void sendRequest(Options options,ResponseCallback callback,JSCallback progressCallback,String instanceId, String bundleURL){
WXRequest wxRequest = new WXRequest();
wxRequest.method = options.getMethod();
wxRequest.url = WXSDKManager.getInstance().getURIAdapter().rewrite(bundleURL, URIAdapter.REQUEST,Uri.parse(options.getUrl())).toString();
wxRequest.body = options.getBody();
wxRequest.timeoutMs = options.getTimeout();
wxRequest.instanceId = instanceId;
if(options.getHeaders()!=null) {
if (wxRequest.paramMap == null) {
wxRequest.paramMap = options.getHeaders();
} else {
wxRequest.paramMap.putAll(options.getHeaders());
}
}
IWXHttpAdapter adapter = ( mAdapter==null) ? WXSDKManager.getInstance().getIWXHttpAdapter() : mAdapter;
if (adapter != null) {
adapter.sendRequest(wxRequest, new StreamHttpListener(callback,progressCallback));
}else{
WXLogUtils.e("WXStreamModule","No HttpAdapter found,request failed.");
}
}
private interface ResponseCallback{
void onResponse(WXResponse response, Map<String, String> headers);
}
private static class StreamHttpListener implements IWXHttpAdapter.OnHttpListener {
private ResponseCallback mCallback;
private JSCallback mProgressCallback;
private Map<String,Object> mResponse = new HashMap<>();
private Map<String,String> mRespHeaders;
private StreamHttpListener(ResponseCallback callback,JSCallback progressCallback) {
mCallback = callback;
mProgressCallback = progressCallback;
}
@Override
public void onHttpStart() {
if(mProgressCallback !=null) {
mResponse.put("readyState",1);//readyState: number 1 OPENED 2 HEADERS_RECEIVED 3 LOADING
mResponse.put("length",0);
mProgressCallback.invokeAndKeepAlive(new HashMap<>(mResponse));
}
}
@Override
public void onHttpUploadProgress(int uploadProgress) {
}
@Override
public void onHeadersReceived(int statusCode,Map<String,List<String>> headers) {
mResponse.put("readyState", 2);
mResponse.put("status", statusCode);
Map<String, String> simpleHeaders = new HashMap<>();
if (headers != null) {
Iterator<Map.Entry<String, List<String>>> it = headers.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, List<String>> entry = it.next();
if (entry.getValue().size() > 0) {
simpleHeaders.put(entry.getKey() == null ? "_" : entry.getKey(), entry.getValue().get(0));
}
}
}
mResponse.put("headers", simpleHeaders);
mRespHeaders = simpleHeaders;
if (mProgressCallback != null) {
mProgressCallback.invokeAndKeepAlive(new HashMap<>(mResponse));
}
}
@Override
public void onHttpResponseProgress(int loadedLength) {
mResponse.put("length",loadedLength);
if(mProgressCallback!=null){
mProgressCallback.invokeAndKeepAlive(new HashMap<>(mResponse));
}
}
@Override
public void onHttpFinish(final WXResponse response) {
//compatible with old sendhttp
if(mCallback!=null){
mCallback.onResponse(response, mRespHeaders);
}
if(WXEnvironment.isApkDebugable()){
WXLogUtils.d("WXStreamModule",response!=null && response.originalData!=null?new String(response.originalData):"response data is NUll!");
}
}
}
}