Você pode explicar o processo de conexão HttpURLConnection?

134

Estou usando HTTPURLConnectionpara me conectar a um serviço da web. Sei usar, HTTPURLConnectionmas quero entender como funciona. Basicamente, quero saber o seguinte:

  • Em que ponto HTTPURLConnectiontenta estabelecer uma conexão com o URL fornecido?
  • Em que ponto posso saber que consegui estabelecer uma conexão com sucesso?
  • O estabelecimento de uma conexão e o envio da solicitação real são feitos em uma etapa / chamada de método? Que método é esse?
  • Você pode explicar a função getOutputStreame getInputStreamno termo do leigo? Eu noto que quando o servidor Eu estou tentando se conectar a está para baixo, eu recebo um Exceptionno getOutputStream. Isso significa que HTTPURLConnectionsó começará a estabelecer uma conexão quando eu chamar getOutputStream? Que tal o getInputStream? Como só consigo obter a resposta em getInputStream, isso significa que ainda não enviei nenhuma solicitação, getOutputStreammas simplesmente estabeleceu uma conexão? Fazer HttpURLConnectionvolta ir para o servidor para solicitação de resposta quando invoco getInputStream?
  • Estou correto em dizer que openConnectionsimplesmente cria um novo objeto de conexão, mas ainda não estabelece nenhuma conexão?
  • Como posso medir a sobrecarga de leitura e conectar a sobrecarga?
Arci
fonte

Respostas:

184
String message = URLEncoder.encode("my message", "UTF-8");

try {
    // instantiate the URL object with the target URL of the resource to
    // request
    URL url = new URL("http://www.example.com/comment");

    // instantiate the HttpURLConnection with the URL object - A new
    // connection is opened every time by calling the openConnection
    // method of the protocol handler for this URL.
    // 1. This is the point where the connection is opened.
    HttpURLConnection connection = (HttpURLConnection) url
            .openConnection();
    // set connection output to true
    connection.setDoOutput(true);
    // instead of a GET, we're going to send using method="POST"
    connection.setRequestMethod("POST");

    // instantiate OutputStreamWriter using the output stream, returned
    // from getOutputStream, that writes to this connection.
    // 2. This is the point where you'll know if the connection was
    // successfully established. If an I/O error occurs while creating
    // the output stream, you'll see an IOException.
    OutputStreamWriter writer = new OutputStreamWriter(
            connection.getOutputStream());

    // write data to the connection. This is data that you are sending
    // to the server
    // 3. No. Sending the data is conducted here. We established the
    // connection with getOutputStream
    writer.write("message=" + message);

    // Closes this output stream and releases any system resources
    // associated with this stream. At this point, we've sent all the
    // data. Only the outputStream is closed at this point, not the
    // actual connection
    writer.close();
    // if there is a response code AND that response code is 200 OK, do
    // stuff in the first if block
    if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
        // OK

        // otherwise, if any other status code is returned, or no status
        // code is returned, do stuff in the else block
    } else {
        // Server returned HTTP error code.
    }
} catch (MalformedURLException e) {
    // ...
} catch (IOException e) {
    // ...
}

As 3 primeiras respostas às suas perguntas são listadas como comentários embutidos, ao lado de cada método, no exemplo HTTP POST acima.

De getOutputStream :

Retorna um fluxo de saída que grava nesta conexão.

Basicamente, acho que você tem uma boa compreensão de como isso funciona, então deixe-me reiterar nos termos dos leigos. getOutputStreambasicamente abre um fluxo de conexão , com a intenção de gravar dados no servidor. No exemplo de código acima, "message" pode ser um comentário que estamos enviando para o servidor que representa um comentário deixado em uma postagem. Quando você vê getOutputStream, está abrindo o fluxo de conexão para gravação, mas na verdade não grava dados até ligar writer.write("message=" + message);.

De getInputStream () :

Retorna um fluxo de entrada que lê desta conexão aberta. Uma SocketTimeoutException pode ser lançada ao ler a partir do fluxo de entrada retornado se o tempo limite da leitura expirar antes que os dados estejam disponíveis para leitura.

getInputStreamfaz o oposto. Assim getOutputStream, ele também abre um fluxo de conexão , mas a intenção é ler dados do servidor, não gravar nele. Se a conexão ou a abertura do fluxo falhar, você verá a SocketTimeoutException.

E o getInputStream? Como só consigo obter a resposta em getInputStream, isso significa que ainda não enviei nenhuma solicitação no getOutputStream, mas simplesmente estabeleci uma conexão?

Lembre-se de que enviar uma solicitação e enviar dados são duas operações diferentes. Quando você chama getOutputStream ou getInputStream url.openConnection() , envia uma solicitação ao servidor para estabelecer uma conexão. Há um handshake que ocorre onde o servidor retorna uma confirmação de que a conexão foi estabelecida. É nesse momento que você está preparado para enviar ou receber dados. Portanto, você não precisa chamar getOutputStream para estabelecer uma conexão para abrir um fluxo, a menos que seu objetivo para fazer a solicitação seja enviar dados.

Nos termos dos leigos, fazer um getInputStreampedido é o equivalente a fazer uma ligação para a casa de seu amigo e dizer: "Ei, tudo bem se eu vier e pedir emprestado esse par de vagens?" e seu amigo estabelece o aperto de mão dizendo: "Claro! Venha e pegue". Então, nesse ponto, a conexão é estabelecida, você caminha para a casa de seu amigo, bate na porta, solicita as garras e volta para sua casa.

Usar um exemplo semelhante getOutputStreamenvolveria ligar para seu amigo e dizer "Ei, eu tenho o dinheiro que lhe devo, posso enviar para você"? Seu amigo, precisando de dinheiro e doente por dentro, que você o guardou por tanto tempo, diz "Claro, vamos lá, seu bastardo barato". Então você vai até a casa do seu amigo e "paga" o dinheiro para ele. Ele então te expulsa e você volta para sua casa.

Agora, continuando com o exemplo do leigo, vejamos algumas exceções. Se você ligou para o seu amigo e ele não estava em casa, pode ser um erro de 500. Se você ligou e recebeu uma mensagem de número desconectado porque seu amigo está cansado de você pedir dinheiro emprestado o tempo todo, é uma página 404 não encontrada. Se seu telefone estiver morto porque você não pagou a conta, isso pode ser uma IOException. (OBSERVAÇÃO: Esta seção pode não estar 100% correta. Pretende fornecer uma idéia geral do que está acontecendo nos termos dos leigos.)

Pergunta 5:

Sim, você está certo de que o openConnection simplesmente cria um novo objeto de conexão, mas não o estabelece. A conexão é estabelecida quando você chama getInputStream ou getOutputStream.

openConnectioncria um novo objeto de conexão. Desde os javadocs URL.openConnection :

Uma nova conexão é aberta sempre, chamando o método openConnection do manipulador de protocolo para este URL.

A conexão é estabelecida quando você chama openConnection, e o InputStream, OutputStream, ou ambos, são chamados quando você os instancia.

Pergunta 6 :

Para medir a sobrecarga, geralmente envolvo um código de temporização muito simples em todo o bloco de conexão, como:

long start = System.currentTimeMillis();
log.info("Time so far = " + new Long(System.currentTimeMillis() - start) );

// run the above example code here
log.info("Total time to send/receive data = " + new Long(System.currentTimeMillis() - start) );

Tenho certeza de que existem métodos mais avançados para medir o tempo e a sobrecarga de solicitação, mas isso geralmente é suficiente para minhas necessidades.

Para obter informações sobre o fechamento de conexões, sobre as quais você não perguntou, consulte Em Java, quando uma conexão de URL é fechada? .

jmort253
fonte
Oi. Obrigado!!! Essa foi realmente uma explicação detalhada e eu realmente aprecio sua resposta. Se entendi sua resposta corretamente, getOutputStream e getInputStream estabelecem uma conexão se nenhuma conexão foi estabelecida ainda. Se eu chamar getOutputStream, em seguida, chamar getInputStream, internamente, HTTPURLConnection não restabelecerá uma conexão no getInputStream, já que eu já consegui estabelecê-lo no getOutStream? HttpURLConnection reutilizará qualquer conexão que eu tenha conseguido estabelecer em getOutputStream em getInputStream.
Arci
Cont .: Ou ele estabelece uma conexão nova e separada para getOutputStream e getInputStream? Além disso, se eu quiser obter a sobrecarga de conexão, o local adequado para colocar meu timer é antes e depois de getOutputStream. Se eu quiser obter a sobrecarga de leitura, o local adequado para colocar meu timer é antes e depois de getInputStream.
Arci
Lembre-se do que o javadoc diz sobre getInputStream e getOutputStream: Returns an output stream that writes to this connection.and Returns an input stream that reads from this open connection.. O fluxo de saída e o fluxo de entrada são separados da conexão.
precisa saber é o seguinte
8
Vale a pena notar que parece que o objeto HttpURLConnection alcança apenas o URL de destino no momento em que PRECISA fazê-lo. No seu exemplo, você tem fluxos de entrada e saída, que obviamente não podem fazer nada até que a conexão esteja aberta. Um caso muito mais simples é uma operação GET, na qual você não faz nada além de inicializar a conexão e depois verificar o código de resposta. Nesse caso, a conexão não é realmente estabelecida até que o método getResponseCode () seja chamado. Caso contrário, esta é uma ótima explicação e exploração do ciclo de vida da conexão!
Spanky Quigman
1
Eu estava confuso antes entre a instância 'UrlConnection' e a conexão TCP / IP / SSL subjacente, dois conceitos separados. O primeiro é basicamente sinônimo de uma única solicitação de página HTTP. Esperamos que o último seja criado apenas uma vez se você estiver fazendo várias solicitações de página para o mesmo servidor.
Tim Cooper
17

Tim Bray apresentou um conciso passo a passo, afirmando que openConnection () não estabelece uma conexão real. Em vez disso, uma conexão HTTP real não é estabelecida até você chamar métodos como getInputStream () ou getOutputStream ().

http://www.tbray.org/ongoing/When/201x/2012/01/17/HttpURLConnection

anônimo
fonte
1

Em que ponto o HTTPURLConnection tenta estabelecer uma conexão com o URL fornecido?

Na porta nomeada no URL, se houver, caso contrário, 80 para HTTP e 443 para HTTPS. Eu acredito que isso está documentado.

Em que ponto posso saber que consegui estabelecer uma conexão com sucesso?

Quando você chama getInputStream () ou getOutputStream () ou getResponseCode () sem obter uma exceção.

O estabelecimento de uma conexão e o envio da solicitação real são feitos em uma etapa / chamada de método? Que método é esse?

Não e nenhum.

Você pode explicar a função de getOutputStream e getInputStream no termo leigo?

Qualquer um deles primeiro se conecta, se necessário, e depois retorna o fluxo necessário.

Percebo que quando o servidor ao qual estou tentando me conectar está inoperante, recebo uma exceção em getOutputStream. Isso significa que HTTPURLConnection só começará a estabelecer uma conexão quando eu chamar getOutputStream? E o getInputStream? Como só consigo obter a resposta em getInputStream, isso significa que ainda não enviei nenhuma solicitação no getOutputStream, mas simplesmente estabeleci uma conexão? O HttpURLConnection volta ao servidor para solicitar resposta quando eu chamo getInputStream?

Veja acima.

Estou correto em dizer que o openConnection simplesmente cria um novo objeto de conexão, mas ainda não estabelece nenhuma conexão?

Sim.

Como posso medir a sobrecarga de leitura e conectar a sobrecarga?

Conectar: ​​reserve o tempo que getInoutStream () ou getOutputStream () leva para retornar, o que você chamar primeiro. Leitura: tempo desde o início da primeira leitura até a obtenção do EOS.

Marquês de Lorne
fonte
1
Acho que OP significava qual ponto de conexão é estabelecida e em que ponto podemos conhecer o status da conexão. O URL da porta não está conectado. Eu estou supondo que isso foi direcionado para openConnection () e getInoutStream () / getOutputStream () / getResponseCode () cuja resposta é posterior.
Aniket Thakur
1

Em que ponto o HTTPURLConnection tenta estabelecer uma conexão com o URL fornecido?

Vale a pena esclarecer, há a instância 'UrlConnection' e a conexão de soquete Tcp / Ip / SSL subjacente , 2 conceitos diferentes. A instância 'UrlConnection' ou 'HttpUrlConnection' é sinônimo de uma única solicitação de página HTTP e é criada quando você chama url.openConnection (). Mas se você fizer vários url.openConnection () da instância 'url', se tiver sorte, eles reutilizarão o mesmo soquete Tcp / Ip e outras coisas de handshake SSL ... o que é bom se você estiver fazendo muitas solicitações de página para o mesmo servidor, especialmente se você estiver usando SSL, onde a sobrecarga de estabelecer o soquete é muito alta.

Consulte: Implementação HttpURLConnection

Tim Cooper
fonte
0

Fiz o exercício para capturar a troca de pacotes de baixo nível e descobri que a conexão de rede só é acionada por operações como getInputStream, getOutputStream, getResponseCode, getResponseMessage etc.

Aqui está a troca de pacotes capturada quando tento escrever um pequeno programa para fazer upload de arquivos no Dropbox.

insira a descrição da imagem aqui

Abaixo está o meu programa de brinquedos e anotação

    /* Create a connection LOCAL object,
     * the openConnection() function DOES NOT initiate
     * any packet exchange with the remote server.
     * 
     * The configurations only setup the LOCAL
     * connection object properties.
     */
    HttpURLConnection connection = (HttpURLConnection) dst.openConnection();
    connection.setDoOutput(true);
    connection.setRequestMethod("POST");
    ...//headers setup
    byte[] testContent = {0x32, 0x32};

    /**
     * This triggers packet exchange with the remote
     * server to create a link. But writing/flushing
     * to a output stream does not send out any data.
     * 
     * Payload are buffered locally.
     */
    try (BufferedOutputStream outputStream = new BufferedOutputStream(connection.getOutputStream())) {
        outputStream.write(testContent);
        outputStream.flush();
    }

    /**
     * Trigger payload sending to the server.
     * Client get ALL responses (including response code,
     * message, and content payload) 
     */
    int responseCode = connection.getResponseCode();
    System.out.println(responseCode);

    /* Here no further exchange happens with remote server, since
     * the input stream content has already been buffered
     * in previous step
     */
    try (InputStream is = connection.getInputStream()) {
        Scanner scanner = new Scanner(is);
        StringBuilder stringBuilder = new StringBuilder();
        while (scanner.hasNextLine()) {
        stringBuilder.append(scanner.nextLine()).append(System.lineSeparator());
        }
    }

    /**
     * Trigger the disconnection from the server.
     */
    String responsemsg = connection.getResponseMessage();
    System.out.println(responsemsg);
    connection.disconnect();
HarryQ
fonte