| /* |
| * 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 android.text.TextUtils; |
| |
| import com.alibaba.fastjson.JSONException; |
| import com.alibaba.fastjson.JSONObject; |
| import com.taobao.weex.WXEnvironment; |
| import com.taobao.weex.WXSDKInstance; |
| 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.performance.WXStateRecord; |
| 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.Locale; |
| 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(Locale.ROOT); |
| 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, final 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"); |
| |
| WXSDKInstance wxsdkInstance = WXSDKManager.getInstance().getSDKInstance(instanceId); |
| if (wxsdkInstance != null) { |
| if (wxsdkInstance.getStreamNetworkHandler() != null) { |
| String localUrl = wxsdkInstance.getStreamNetworkHandler().fetchLocal(url); |
| if (!TextUtils.isEmpty(localUrl)) { |
| url = localUrl; |
| } |
| } |
| } |
| |
| if (method != null) method = method.toUpperCase(Locale.ROOT); |
| 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); |
| WXStateRecord.getInstance().recordAction(instanceId,"stream response code:"+(null!= response?response.statusCode:"null")); |
| 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(Locale.ROOT)); |
| } |
| } |
| |
| |
| |
| static String readAsString(byte[] data,String cType){ |
| String charset = "utf-8"; |
| if(cType != null){ |
| Matcher matcher = CHARSET_PATTERN.matcher(cType.toLowerCase(Locale.ROOT)); |
| 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) { |
| continue; |
| } else if (entry.getValue().size() == 1) |
| simpleHeaders.put(entry.getKey() == null ? "_" : entry.getKey(), entry.getValue().get(0)); |
| else { |
| simpleHeaders.put(entry.getKey() == null ? "_" : entry.getKey(), entry.getValue().toString()); |
| } |
| } |
| } |
| |
| 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!"); |
| } |
| } |
| } |
| |
| } |