[컴][os][안드로이드] 안드로이드에서 Http request 시에 IP address 가 여러개라면 ?

android dns policy / java jvm dns caching  / dns cache / what ip address is used when the returned ip address from dns query is several / URL.openConnection / http request 시 dns query 의 결과로 오는 ipaddress 가 여러개인 경우




안드로이드에서 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);
    }

// HttpsURLConnectionImpl:374
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;
}






댓글 없음:

댓글 쓰기