/* * Copyright © Yan Zhenjie. All Rights Reserved * * Licensed 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.yanzhenjie.nohttp; import android.text.TextUtils; import android.webkit.URLUtil; import com.yanzhenjie.nohttp.error.NetworkError; import com.yanzhenjie.nohttp.error.TimeoutError; import com.yanzhenjie.nohttp.error.URLError; import com.yanzhenjie.nohttp.error.UnKnownHostError; import com.yanzhenjie.nohttp.rest.Request; import com.yanzhenjie.nohttp.tools.IOUtils; import com.yanzhenjie.nohttp.tools.NetUtil; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.SocketTimeoutException; import java.net.URI; import java.net.URL; import java.net.UnknownHostException; import java.util.List; import java.util.Map; /** * Created by Yan Zhenjie on 2016/9/4. */ public class HttpConnection { private NetworkExecutor mExecutor; public HttpConnection(NetworkExecutor executor) { this.mExecutor = executor; } /** * Send the request, send only head, parameters, such as file information. * * @param request {@link IBasicRequest}. * @return {@link Connection}. */ public Connection getConnection(IBasicRequest request) { Logger.d("--------------Request start--------------"); Headers responseHeaders = new HttpHeaders(); InputStream inputStream = null; Exception exception = null; Network network = null; String url = request.url(); try { if (!NetUtil.isNetworkAvailable()) throw new NetworkError("The network is not available, please check the network. The requested url is:" + " " + url); // MalformedURLException, IOException, ProtocolException, UnknownHostException, SocketTimeoutException network = createConnectionAndWriteData(request); Logger.d("-------Response start-------"); int responseCode = network.getResponseCode(); responseHeaders = parseResponseHeaders(new URI(request.url()), responseCode, network.getResponseHeaders()); // handle body if (responseCode == 301 || responseCode == 302 || responseCode == 303 || responseCode == 307) { Connection redirectConnection = handleRedirect(request, responseHeaders); responseHeaders = redirectConnection.responseHeaders(); inputStream = redirectConnection.serverStream(); exception = redirectConnection.exception(); } else if (hasResponseBody(request.getRequestMethod(), responseCode)) { inputStream = network.getServerStream(responseCode, responseHeaders); } Logger.d("-------Response end-------"); } catch (MalformedURLException e) { exception = new URLError("The url is malformed: " + url + "."); } catch (UnknownHostException e) { exception = new UnKnownHostError("Hostname can not be resolved: " + url + "."); } catch (SocketTimeoutException e) { exception = new TimeoutError("Request time out: " + url + "."); } catch (Exception e) { exception = e; } finally { if (exception != null) Logger.e(exception); } Logger.d("--------------Request finish--------------"); return new Connection(network, responseHeaders, inputStream, exception); } /** * Handle retries, and complete the request network here. * * @param request {@link IBasicRequest}. * @return {@link Network}. * @throws Exception {@link #createNetwork(IBasicRequest)}. */ private Network createConnectionAndWriteData(IBasicRequest request) throws Exception { Network network = null; Exception exception = null; int retryCount = request.getRetryCount() + 1; boolean failed = true; for (; failed && retryCount > 0; retryCount--) { try { network = createNetwork(request); exception = null; failed = false; } catch (Exception e) { exception = e; } } if (failed) { throw exception; } else if (request.getRequestMethod().allowRequestBody()) { writeRequestBody(request, network.getOutputStream()); } return network; } /** * The connection is established, including the head and send the request body. * * @param request {@link IBasicRequest}. * @return {@link HttpURLConnection} Have been established and the server connection, and send the complete data, * you can directly determine the response code and read the data. * @throws Exception can happen when the connection is established and send data. */ private Network createNetwork(IBasicRequest request) throws Exception { // Pre operation notice. request.onPreExecute(); // Print url, method. String url = request.url(); Logger.i("Request address: " + url); Logger.i("Request method: " + request.getRequestMethod()); Headers headers = request.headers(); headers.set(Headers.HEAD_KEY_CONTENT_TYPE, request.getContentType()); // Connection. List<String> values = headers.getValues(Headers.HEAD_KEY_CONNECTION); if (values == null || values.size() == 0) headers.add(Headers.HEAD_KEY_CONNECTION, Headers.HEAD_VALUE_CONNECTION_KEEP_ALIVE); // Content-Length. RequestMethod requestMethod = request.getRequestMethod(); if (requestMethod.allowRequestBody()) headers.set(Headers.HEAD_KEY_CONTENT_LENGTH, Long.toString(request.getContentLength())); // Cookie. headers.addCookie(new URI(url), NoHttp.getCookieManager()); return mExecutor.execute(request); } /** * Write request params. * * @param request {@link IBasicRequest}. * @param outputStream {@link OutputStream}. * @throws IOException io exception. */ private void writeRequestBody(IBasicRequest request, OutputStream outputStream) throws IOException { // 6. Write request body Logger.i("-------Send request data start-------"); OutputStream realOutputStream = IOUtils.toBufferedOutputStream(outputStream); request.onWriteRequestBody(realOutputStream); IOUtils.closeQuietly(realOutputStream); Logger.i("-------Send request data end-------"); } /** * The redirection process any response. * * @param oldRequest need to redirect the {@link Request}. * @param responseHeaders need to redirect the request of the responding head. * @return {@link Connection}. */ private Connection handleRedirect(IBasicRequest oldRequest, Headers responseHeaders) { // redirect request IBasicRequest redirectRequest = null; RedirectHandler redirectHandler = oldRequest.getRedirectHandler(); if (redirectHandler != null) { if (redirectHandler.isDisallowedRedirect(responseHeaders)) return new Connection(null, responseHeaders, null, null); else { redirectRequest = redirectHandler.onRedirect(responseHeaders); } } if (redirectRequest == null) { String location = responseHeaders.getLocation(); if (!URLUtil.isNetworkUrl(location)) { String oldUrl = oldRequest.url(); try { URL url = new URL(oldUrl); location = url.getProtocol() + "://" + url.getHost() + location; } catch (MalformedURLException e) { // nothing. } } redirectRequest = new BasicRequest(location, oldRequest.getRequestMethod()) { }; redirectRequest.setRedirectHandler(oldRequest.getRedirectHandler()); redirectRequest.setSSLSocketFactory(oldRequest.getSSLSocketFactory()); redirectRequest.setHostnameVerifier(oldRequest.getHostnameVerifier()); redirectRequest.setParamsEncoding(oldRequest.getParamsEncoding()); redirectRequest.setProxy(oldRequest.getProxy()); } return getConnection(redirectRequest); } /** * Parse server response headers, here will save cookies. * * @param uri according to the requested URL generated uris. * @param responseCode responseCode. * @param responseHeaders responseHeaders of server. * @return response headers of server. */ private Headers parseResponseHeaders(URI uri, int responseCode, Map<String, List<String>> responseHeaders) { // handle cookie try { NoHttp.getCookieManager().put(uri, responseHeaders); } catch (IOException e) { Logger.e(e, "Save cookie filed: " + uri.toString() + "."); } // handle headers Headers headers = new HttpHeaders(); headers.set(responseHeaders); headers.set(Headers.HEAD_KEY_RESPONSE_CODE, Integer.toString(responseCode)); // print for (String headKey : headers.keySet()) { List<String> headValues = headers.getValues(headKey); for (String headValue : headValues) { StringBuilder builder = new StringBuilder(); if (!TextUtils.isEmpty(headKey)) builder.append(headKey).append(": "); if (!TextUtils.isEmpty(headValue)) builder.append(headValue); Logger.i(builder.toString()); } } return headers; } ////////// Read response body ////////// /** * This requestMethod and responseCode has responseBody ? * * @param requestMethod it's come from {@link RequestMethod}. * @param responseCode responseCode from server. * @return true: there is data, false: no data. */ public static boolean hasResponseBody(RequestMethod requestMethod, int responseCode) { return requestMethod != RequestMethod.HEAD && hasResponseBody(responseCode); } /** * According to the response code to judge whether there is data. * * @param responseCode responseCode. * @return true: there is data, false: no data. */ public static boolean hasResponseBody(int responseCode) { return !(100 <= responseCode && responseCode < 200) && responseCode != 204 && responseCode != 205 && !(300 <= responseCode && responseCode < 400); } }