안드로이드에서 URLConnection() 등을 이용해서 http request 를 할 때 dns query 에서 가져온 ip address 가 여러개 일 때 어느 것을 이용할까?
결론은 되는 것이 나올 때까지 하나씩 시도 해 본다. 좀 더 구체적인 부분은 아래 소스 분석을 참고하자.
참고로 여기서 사용한 소스는 android 4.1.2 소스이다. 5.1 부터는 구조가 조금 다르다.
안드로이드에서 HTTP request
기본적으로 안드로이드(굳이 따지고 들자면 java)에서 HTTP request 는 아래처럼 code 를 짠다.URL feedUrl = new URL(feedUrlString); HttpURLConnection conn = (HttpURLConnection) feedUrl.openConnection(); conn.setReadTimeout(10000 /* milliseconds */); conn.setConnectTimeout(15000 /* milliseconds */); conn.setRequestMethod(requestMethod); conn.setDoInput(true); return conn.getInputStream();
HttpsHandler.openConnection
openConnection() 부분에서는 실제 connection 이 일어나지 않는다.HttpsHandler.openConnection
public final class HttpsHandler extends URLStreamHandler { @Override protected URLConnection openConnection(URL url) throws IOException { return new HttpsURLConnectionImpl(url, getDefaultPort()); } @Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException { if (url == null || proxy == null) { throw new IllegalArgumentException("url == null || proxy == null"); } return new HttpsURLConnectionImpl(url, getDefaultPort(), proxy); } @Override protected int getDefaultPort() { return 443; } }
final class HttpsURLConnectionImpl extends HttpsURLConnection { /** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl */ private final HttpUrlConnectionDelegate delegate; protected HttpsURLConnectionImpl(URL url, int port) { super(url); delegate = new HttpUrlConnectionDelegate(url, port); } protected HttpsURLConnectionImpl(URL url, int port, Proxy proxy) { super(url); delegate = new HttpUrlConnectionDelegate(url, port, proxy); }
private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl { private HttpUrlConnectionDelegate(URL url, int port) { super(url, port); } private HttpUrlConnectionDelegate(URL url, int port, Proxy proxy) { super(url, port, proxy); } @Override protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders, HttpConnection connection, RetryableOutputStream requestBody) throws IOException { return new HttpsEngine(this, method, requestHeaders, connection, requestBody, HttpsURLConnectionImpl.this); } public SecureCacheResponse getCacheResponse() { HttpsEngine engine = (HttpsEngine) httpEngine; return engine != null ? (SecureCacheResponse) engine.getCacheResponse() : null; } public SSLSocket getSSLSocket() { HttpsEngine engine = (HttpsEngine) httpEngine; return engine != null ? engine.sslSocket : null; } }
class HttpURLConnectionImpl extends HttpURLConnection { private final int defaultPort; private Proxy proxy; private final RawHeaders rawRequestHeaders = new RawHeaders(); private int redirectionCount; protected IOException httpEngineFailure; protected HttpEngine httpEngine; protected HttpURLConnectionImpl(URL url, int port) { super(url); defaultPort = port; } protected HttpURLConnectionImpl(URL url, int port, Proxy proxy) { this(url, port); this.proxy = proxy; }
public abstract class HttpURLConnection extends URLConnection { ... protected HttpURLConnection(URL url) { super(url); } public abstract class URLConnection { ... protected URLConnection(URL url) { this.url = url; }
HttpURLConnection.getInputStream()
실제 connect() 는 getInputStream() 을 호출할 때 일어나게 되는데 이제부터 getInputStream() 을 한 번 살펴보자.getInputStream() 을 호출하게 되면 아래의 순서로 함수를 호출하게 된다.(자세한 부분은 아래 code 부분의 노란색을 따라가자.)
HttpURLConnectionImpl.getInputStream
--> getResponse()
--> httpEngine.sendRequest()
--> connect()
--> openSocketConnection()
--> HttpConnection.connect()
--> HttpConnectionPool.INSTANCE.get()
--> address.connect();
--> InetAddress.getAllByName()
결국getAllByName() 을 호출하고, 거기서 받아온 address 를 전부 시도하는 듯 하다. 실질적인 test 는 못해봤기 때문에 확실치는 않지만, code 만으로 분석한 결과는 그렇다.
// HttpURLConnectionImpl.java
@Override
public final InputStream getInputStream() throws IOException {
if (!doInput) {
throw new ProtocolException("This protocol does not support input");
}
HttpEngine response = getResponse();
/*
* if the requested file does not exist, throw an exception formerly the
* Error page from the server was returned if the requested file was
* text/html this has changed to return FileNotFoundException for all
* file types
*/
if (getResponseCode() >= HTTP_BAD_REQUEST) {
throw new FileNotFoundException(url.toString());
}
InputStream result = response.getResponseBody();
if (result == null) {
throw new IOException("No response body exists; responseCode=" + getResponseCode());
}
return result;
}
private HttpEngine getResponse() throws IOException {
initHttpEngine();
...
while (true) {
try {
httpEngine.sendRequest();
httpEngine.readResponse();
} catch (IOException e) {
...
}
...
}
}
// HttpURLConnectionImpl.java
private void initHttpEngine() throws IOException {
....
connected = true;
try{
...
httpEngine = newHttpEngine(method, rawRequestHeaders, null, null);
}
// HttpURLConnectionImpl.java
protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
HttpConnection connection, RetryableOutputStream requestBody) throws IOException {
return new HttpEngine(this, method, requestHeaders, connection, requestBody);
}
// HttpEngine.java
public HttpEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
HttpConnection connection, RetryableOutputStream requestBodyOut) throws IOException {
...
this.requestHeaders = new RequestHeaders(uri, new RawHeaders(requestHeaders));
}
// RequestHeaders.java
public RequestHeaders(URI uri, RawHeaders headers) {
...
HeaderParser.CacheControlHandler handler = new HeaderParser.CacheControlHandler() {
@Override public void handle(String directive, String parameter) {
if (directive.equalsIgnoreCase("no-cache")) {
noCache = true;
...
}
...
}
}
// Header related variable 들
for (int i = 0; i < headers.length(); i++) {
String fieldName = headers.getFieldName(i);
String value = headers.getValue(i);
if ("Cache-Control".equalsIgnoreCase(fieldName)) {
HeaderParser.parseCacheControl(value, handler);
} else if ("Pragma".equalsIgnoreCase(fieldName)) {
if (value.equalsIgnoreCase("no-cache")) {
noCache = true;
}
} else if ("If-None-Match".equalsIgnoreCase(fieldName)) {
ifNoneMatch = value;
}
...
}
// HttpEngine.java
public final void sendRequest() throws IOException {
if (responseSource != null) {
return;
}
prepareRawRequestHeaders();
initResponseSource();
...
if (responseSource.requiresConnection()) {
sendSocketRequest();
} else if (connection != null) {
HttpConnectionPool.INSTANCE.recycle(connection);
connection = null;
}
}
// HttpEngine.java
private void sendSocketRequest() throws IOException {
if (connection == null) {
connect();
}
...
}
protected void connect() throws IOException {
if (connection == null) {
connection = openSocketConnection();
}
}
protected final HttpConnection openSocketConnection() throws IOException {
HttpConnection result = HttpConnection.connect(uri, getSslSocketFactory(),
policy.getProxy(), requiresTunnel(), policy.getConnectTimeout());
Proxy proxy = result.getAddress().getProxy();
if (proxy != null) {
policy.setProxy(proxy);
}
result.setSoTimeout(policy.getReadTimeout());
return result;
}
// HttpConnection.java
public static HttpConnection connect(URI uri, SSLSocketFactory sslSocketFactory,
Proxy proxy, boolean requiresTunnel, int connectTimeout) throws IOException {
/*
* Try an explicitly-specified proxy.
*/
if (proxy != null) {
Address address = (proxy.type() == Proxy.Type.DIRECT)
? new Address(uri, sslSocketFactory)
: new Address(uri, sslSocketFactory, proxy, requiresTunnel);
return HttpConnectionPool.INSTANCE.get(address, connectTimeout);
}
...
...
public static final class Address {
...
public HttpConnection connect(int connectTimeout) throws IOException {
return new HttpConnection(this, connectTimeout);
}
}
}
// HttpConnectionPool.java
public HttpConnection get(HttpConnection.Address address, int connectTimeout)
throws IOException {
...
synchronized (connectionPool) {
...
}
/*
* We couldn't find a reusable connection, so we need to create a new
* connection. We're careful not to do so while holding a lock!
*/
return address.connect(connectTimeout);
}
// HttpConnection.java
private HttpConnection(Address config, int connectTimeout) throws IOException {
this.address = config;
/*
* Try each of the host's addresses for best behavior in mixed IPv4/IPv6
* environments. See http://b/2876927
* TODO: add a hidden method so that Socket.tryAllAddresses can does this for us
*/
Socket socketCandidate = null;
InetAddress[] addresses = InetAddress.getAllByName(config.socketHost);
for (int i = 0; i < addresses.length; i++) {
socketCandidate = (config.proxy != null && config.proxy.type() != Proxy.Type.HTTP)
? new Socket(config.proxy)
: new Socket();
try {
socketCandidate.connect(
new InetSocketAddress(addresses[i], config.socketPort), connectTimeout);
break;
} catch (IOException e) {
if (i == addresses.length - 1) {
throw e;
}
}
}
this.socket = socketCandidate;
}
댓글 없음:
댓글 쓰기