2014-04-28 3 views
2

http 클라이언트 요청을 디버그하려고합니다. 연결이 이루어질 때 사용 된 로컬 포트 ​​번호를 가져와야합니다. HttpClientContext를 사용하여 연결을 가져오고 연결이 성공하면 로컬 포트를 검색 할 수 있습니다. 그러나 IOException이 발생하는 경우 로컬 포트 ​​번호를 검색 할 때 ConnectionShutdownException이 발생합니다. 오류 발생시 모든 http 요청에 대해 로컬 포트 ​​번호를 얻을 수있는 방법에 대한 단서.Apache InternalHttpClient : 로컬 포트 ​​번호 확인

+0

'ConnectionShutdownException'은 연결이 설정되지 않았거나 연결을 종료시키는 어떤 문제로 인해 연결이 현재 존재하지 않음을 의미합니다. 연결이 없으면 열린 포트가 없으며 정의 상 포트 번호가 없습니다. – Baldy

답변

2

이것은 HTTPClient 4.0.1 (나와 함께있는 마지막 버전) 용입니다. 간단한 하나의 라이너를 찾지 못했습니다 ...

소켓을 로컬 호스트/포트에 실제로 바인드하고 원격 호스트/포트에 연결하는 부분은 당연히 SocketFactory입니다. HTTPClient에서 소켓 팩토리는 SchemeRegistry에 연결되며, 연결 관리자는 차례로 연결 관리자에 속합니다. HTTPClient의 SocketFactory는 javax.net.SocketFactory가 아니라 그러한 객체를 감싸는 래퍼입니다.

물론
SchemeRegistry schRgstr = new SchemeRegistry(); 
Scheme httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80); 
schRgstr.register(httpScheme); 

는 org.apache.http.conn.scheme.SocketFactory는 인터페이스입니다, 그래서 당신은이 편리 올 것이다 당신이 특히 원하는 무엇이든 할 그것을 장식 할 수 있습니다 : 당신은 너무 같은 방식을 정의 .

소켓 팩토리를 호출하는 httpclient의 부분은 ClientConnectionOperator (인터페이스이기도 함)이라고합니다. 이 객체는 실제로 클라이언트가 아닌 이 아닌 연결 관리자와 연결되어 있습니다. 당신이 연결 연산자를 정의하려는 경우, 당신은, 예를 들어, 같은 (익명 클래스)도 연결 관리자를 오버라이드 (override) 할 수 있습니다 그래서 :

    :

    ThreadSafeClientConnManager connMngr = new ThreadSafeClientConnManager(httpParams, schRgstr) { 
        protected ClientConnectionOperator createConnectionOperator(SchemeRegistry schreg) { 
        return new YourConnectionOperator(schreg); 
        }; 
    }; 
    

    소켓의 라이프 사이클 모델과 같이 간다

  1. 연결 관리자가 지금 연결이 필요할 때 운영자에게 createConnection을 호출합니다 (기본적으로 실제 소켓을 보유 할 내부 개체를 만들지 만 아무것도하지 않습니다).
  2. 은 또한 길을 따라 그 다음, openConnection
  3. 대해서 openConnection가 SocketFactory로 이동하고 새로운 소켓을 요청 호출과 같이 연결을 시도

    sock = sf.connectSocket(sock, target.getHostName(), 
         schm.resolvePort(target.getPort()), 
         local, 0, params); 
    
  4. (여기서, SF는 "HttpClient를 소켓 팩토리"입니다)
  5. 이 호출이 실패하면 예외가 발생하고 더 많은 정보에 액세스 할 수 없습니다. 우리는 그걸로 돌아갈거야.

  6. connectSocket이 작동하는 경우 연결 연산자에서 prepareSocket 메서드가 호출됩니다. 그래서, 당신은 방법을 무시하고 상황에 포트 정보를 넣어 (또는 아무것도 당신 공상) :

    @Override 
    protected void prepareSocket(Socket sock, HttpContext context, HttpParams params) throws IOException { 
        super.prepareSocket(sock, context, params); 
        context.setAttribute("LOCAL PORT INTERCEPTOR", sock.getLocalPort()); 
    } 
    

당신이 HttpClient를 호출 할 때, 그래서 전달 사용되는 HttpContext를 예 다른 예외 때문에 나중에 호출이 실패하더라도이를 액세스 할 수 있습니다. 당신이 당신의 전화를 걸 때, 그래서 그것을 만들 :

HttpContext ctx = new BasicHttpContext(); 
HttpGet get = new HttpGet(targetUrl.getUrl()); 
HttpResponse resp = client.execute(get, ctx); 

를이 코드에서, 클라이언트는 5 단계로까지 갈 수 있다면, 당신은 예외가 나중에 발생하는 경우에도 포트 정보에 액세스 할 수 있습니다 (연결 드롭 아웃 , 시간 초과, 유효하지 않은 HTTP 등).가는

이 모든 어두운 영역이 여전히있다 : 전화 4 단계에서 실패하는 경우 (소켓의 실제 개방, 같은 대상 호스트 이름을 해결하는 동안 당신은 DNS 오류가있는 경우) ... 나는이 사건이 실제로 당신에게 흥미로운 지 확신하지 못합니다. (왜 그런지는 알 수 없습니다.) 그것을 다루는 것을 보는 것이 "지저분 해"지기 시작하면, 여러분은 이것을 필요로하거나 그렇지 않은지 정말로 고려해야합니다.

우리는 정말 깊은 우선 작업을 시작해야하며, 많은 작업이 필요합니다. 그 중 일부는 아주 좋은 디자인을 고려하지 않았습니다.

HTTPClient 작성자가 필요한 정보를 얻기 위해 재정의 할 수있는 필요한 메서드를 제공하지 않아 복잡성이 발생합니다. 소켓 공장 내부, 흥미로운 점은 다음과 같습니다

sock = createSocket(); 
InetSocketAddress isa = new InetSocketAddress(localAddress, localPort); 
sock.bind(isa); 
// go on and connect to the server 
// like socket.connect... 

, 로컬 및 소켓 열리는의 서버 측을 분할 재정의 할 방법은 없습니다 그래서 모든 소켓 예외는 서버 측에서 발생하는 경우, 당신을 소켓 인스턴스에 대한 액세스가 손실되고 로컬 포트 ​​정보가 사라집니다.

우리가 할 수있는 엔트리 포인트가 하나 있기 때문에 모든 것이 손실되지 않습니다 : createSocket 메소드! 기본 구현은

public Socket createSocket() { 
    return new Socket(); 
} 

입니다 그러나 소켓 최종 클래스 아니므로, 당신은 ... 그것으로 재생할 수 있습니다!

public Socket createSocket() { 
    return new Socket() { 
     @Override 
     public void bind(SocketAddress bindpoint) throws IOException { 
     super.bind(bindpoint); 
     // get the local port and give the info back to whomever you like 
     } 
    }; 
} 

문제는 다음과 같습니다 (우리는 익명의 하위 클래스를 만들 수 있기 때문에)이 일반 소켓 작동하지만, 당신은 단순히이 경우에 소켓을 실체화 할 수 없기 때문에 이것은, 당신이해야 할, HTTPS 작동하지 않습니다 :

return (SSLSocket) this.javaxNetSSLSocketFactory.createSocket(); 

그런 경우 익명 하위 클래스를 만들 수 없습니다. 그리고 Socket은 인터페이스가 없기 때문에 프록시로 꾸미기조차 할 수 없습니다. 그래서 (혹독한 코드도) SSLSocket에 랩핑하고 위임하는 Socket 서브 클래스를 만들 수 있습니다. 그러나 그것은 필사적입니다.

그래서 정리 시간.

서버에 연결되어있는 소켓에 대해서만 신경 쓰면 솔루션은 매우 간단합니다.

ConnectionOperator을 덮어 쓰는 사용자 지정 ConnectionManager에서 우리가 작성한 체계 레지스트리가 사용됩니다. 운영자가 재정의하는 메서드는 prepareSocket이며 포트 정보와 함께 전송되는 요청의 HttpContext을 간단히 업데이트 할 수 있습니다. 모든 것이 잘되면 정보를 쉽게 찾을 수있는 방법입니다.

절대로, 연결되어 있지 않은 (내가 제한적으로 사용한다고 주장하는) 소켓에 할당 된 로컬 포트를 신경 쓰고 싶다면, 더 깊게 갈 필요가 있습니다.

당사의 SchemeRegistry은 맞춤 SocketFactories을 갖도록 설계되어야합니다. 이것들은 아마도 데코레이터 또는 디폴트 것이어야합니다 ...createSocket 오버라이드 (override) 메소드를 사용하면 로컬 포트의 바인딩을 가로 채고 (ConcurrentMap<Socket, Integer> 또는 ThreadLocal에 저장) 'connectSocket'을 오버라이드하여 발생할 수있는 모든 예외를 트랩하고 다시 브로드 캐스팅 할 수 있습니다 로컬 포트 ​​정보를 보유 할 수있는 자체 예외 유형으로 랩핑합니다. 그런 식으로 예외를 통과해도 클라이언트를 호출 할 때 원인 체인을 확인하면 포트 데이터를 찾을 수 있습니다. 예외가 발생하지 않으면 인스턴스/스레드 로컬을 정리하거나 매핑합니다.

+0

아주 좋지만 javax.net.SocketFactory를 의미합니까? – EJP

+0

아니오, 위의 대부분은 HTTPClient의 SocketFactory 인터페이스를 의미합니다. HTTP의 경우 기본 구현은 직접 (새로운 Socket()) 직접 소켓을 만들지 만 HTTPS의 경우 org.apache ... SocketFactory는 실제로 javax.net (SSL) SocketFactory를 래핑합니다. – GPI