1. Antecedentes
El protocolo HTTP es un protocolo sin estado, es decir, cada solicitud es independiente entre sí. Por lo tanto, su implementación inicial es que cada solicitud HTTP abrirá una conexión de socket TCP, y la conexión se cerrará después de que se complete la interacción.
El protocolo HTTP es un protocolo dúplex completo, por lo que se necesitan tres apretones de manos y cuatro ondas para establecer y desconectarse. Obviamente, en este diseño, cada vez que envío una solicitud HTTP, consume muchos recursos adicionales, a saber, el establecimiento y la destrucción de la conexión.
Por lo tanto, también se ha desarrollado el protocolo HTTP, y la multiplexación de conexión al socket se realiza a través de métodos de conexión persistentes.
De la imagen, puedes ver:
Hay dos implementaciones de conexiones persistentes: Conexiones de Alive y Persistente de HTTP/1.1 para HTTP/1.0+.
2. Manténgase alive para http/1.0+
Desde 1996, muchos navegadores y servidores HTTP/1.0 han extendido el protocolo, es decir, el protocolo de extensión "mantener alive".
Tenga en cuenta que este protocolo de extensión aparece como un complemento para 1.0 "conexión persistente experimental". Keep-Alive ya no se usa, y no se explica en la última especificación HTTP/1.1, pero muchas aplicaciones han continuado.
Clientes que usan HTTP/1.0 Agregue "Conexión: Keep-Alive" al encabezado y solicite al servidor que mantenga una conexión abierta. Si el servidor está dispuesto a mantener esta conexión abierta, incluirá el mismo encabezado en la respuesta. Si la respuesta no contiene el encabezado "Conexión: mantener alive", el cliente pensará que el servidor no admite Keep-Alive y cerrará la conexión actual después de enviar el mensaje de respuesta.
A través del protocolo suplementario de Keep-Alive, se completa una conexión persistente entre el cliente y el servidor, pero todavía hay algunos problemas:
3. Conexión persistente de HTTP/1.1
HTTP/1.1 toma un método de conexión persistente para reemplazar Keep-Alive.
Las conexiones HTTP/1.1 son persistentes de forma predeterminada. Si desea cerrar explícitamente, debe agregar la conexión: Cerrar el encabezado al mensaje. Es decir, en http/1.1, todas las conexiones se multiplexan.
Sin embargo, como Keep-Alive, las conexiones persistentes inactivas pueden ser cerradas por el cliente y el servidor en cualquier momento. No enviar conexión: Cerrar no significa que el servidor promete que la conexión permanecerá abierta para siempre.
4. Cómo generar conexiones persistentes por httpclient
HttpClien utiliza un grupo de conexión para administrar las conexiones de retención. En el mismo enlace TCP, las conexiones se pueden reutilizar. HttpClient conecta la persistencia a través de la agrupación de conexión.
De hecho, la tecnología de "grupo" es un diseño general, y su idea de diseño no es complicada:
Todos los grupos de conexión tienen esta idea, pero cuando observamos el código fuente de HTTPClient, nos centramos principalmente en dos puntos:
4.1 Implementación del grupo de conexión HTTPClient
El procesamiento de conexiones persistentes de HttpClient se puede reflejar en el siguiente código. Lo siguiente es extraer las piezas relacionadas con el grupo de conexión de MainClientExec y eliminar otras partes:
clase pública MainClientExec implementa ClientExeCchain {@Override público ClosableHttTPResponse Execute (ruta final httproute, final httprequestwraperper, contexto final httpClientContext, contexto final httpExeCutionAware execaWare) lanza ioexception, httpexception {// get a una solicitud de la conexión de la conexión de la conexión htttetConnection ConnectionRequest final ConnectionRequest Connrequest = connmanager.RequestConnection (ruta, userToken); final httpclientconnection gestedConn; final int timeout = config.getConnectionRequestTimeOut (); // Obtenga una conexión administrada desde la solicitud de conexión ConnectionRequesthttpClientConnection ManagedConn = connrequest.get (Tiempo de espera> 0? Tiempo de espera: 0, TimeUnit.MilliseConds); // Enviar el administrador de conexión httpclientconnectionsManager y la conexión administrada httpclientconnection a un thitor de conexión posee el toldeo final connholder = new ConnectionHolder (this.log, this.connmanager, managedConn); intente {httpresponse respuesta; if (! ManagedConn.ISopen ()) {// Si la conexión administrada actualmente no está en un estado abierto, debe restablecer la conexión establecida (proxyauthstate, ganagedconn, ruta, solicitud, contexto); } // Enviar solicitud a través de la conexión httpClientConnection respuesta = requestExeCutor.Execute (Solicitar, ManagedConn, context); // Distinga si la conexión se puede reutilizar a través de la estrategia de reutilización de la conexión si (reusestrategy.keepalive (respuesta, contexto)) {// Obtener el período de validez de la conexión duración final = keepAlivestrategy.getkeepaliveDury (respuesta, contexto); // Establecer el período de validez de conexión connholder.setValidfor (duración, timeUnit.milliseConds); // Marque la conexión actual como el estado reutilizable connholder.markreUsable (); } else {connholder.marknonreUsable (); }} final httpentity entity = respuesta.getEntity (); if (entity == null || devolver nuevo httpresponseproxy (respuesta, nulo); } else {return new httPResponseProxy (respuesta, connholder); }}Aquí vemos que el procesamiento de conexiones durante el proceso de solicitud HTTP es consistente con las especificaciones de protocolo. Aquí discutiremos la implementación específica.
PoolinghttpClientConnectionManager es el administrador de conexión predeterminado de httpclient. Primero, obtenga una solicitud de conexión a través de requestConnection (). Tenga en cuenta que esta no es una conexión.
Public ConnectionRequest RequestConnection (ruta final httproute, estado del objeto final) {Future final <cpoolEntry> futuro = this.pool.lease (ruta, estado, nulo); return new ConnectionRequest () {@Override public boolean cancel () {return Future.cancel (true); } @Override public HttpClientConnection get (Tiempo de tiempo largo final, Tunit Final Timit) lanza InterruptedException, ExecutionException, ConnectionPoolTimeOutException {final httpClientConnection Conn = leaseconnection (futuro, tiempo de espera, tunic); if (conn.isopen ()) {host final httphost; if (ruta.getProxyHost ()! = null) {host = ruta.getProxyHost (); } else {host = ruta.getTargethost (); } final SocketConfig SocketConfig = resueldSockConfig (host); conn.setsocketTimeout (SocketConfig.getSotimeOut ()); } return Conn; }}; }Puede ver que el objeto ConnectionRequest devuelto es en realidad una instancia de conexión real que contiene el futuro <cpoolEntry>, que es administrado por el grupo de conexión.
Del código anterior en el que debemos centrarnos:
Future<CPoolEntry> future = this.pool.lease(route, state, null)
Cómo obtener una conexión asíncrona de un grupo de conexión Cpool, Future <CpoolEntry>
HttpClientConnection conn = leaseConnection(future, timeout, tunit)
Cómo obtener una verdadera conexión con una conexión asíncrona con el futuro <cpoolEntry>
4.2 Futuro <CpoolEntry>
Echemos un vistazo a cómo Cpool lanza un futuro <cpoolEntry>. El código central de AbstractConnpool es el siguiente:
Private E getpoolEntryBlocking (ruta T final, estado final del objeto, tiempo de espera largo final, Tunit de tiempo de tiempo final, Future Future Final <E> Future) lanza IOException, InterruptedException, TimeOutException {// Bloquear primero el grupo de conexión actual. El bloqueo actual es un reentrantlockthis.lock.lock (); Pruebe {// Obtenga un grupo de conexión correspondiente al HTTProute actual. Para el conjunto de conexión de HttpClient, el grupo total tiene un tamaño, y la conexión correspondiente a cada ruta también es una piscina, por lo que es un "grupo en la piscina" rutespecífico final <t, c, e> piscina = getpool (ruta); E entrada; para (;;) {afirmars.check (! this.isshutdown, "Pool de conexión apagado"); // Obtenga conexiones desde el grupo correspondiente a la ruta, que puede ser nula, o una entrada válida de entrada = Pool.getFree (estado); // Si obtiene nulo, salga el bucle if (entry == null) {break; } // Si obtiene una conexión caducada o la conexión se ha cerrado, suelte el recurso y continúe con el bucle para obtener if (Entry.isExPired (System.CurrentTimemillis ())) {Entry.close (); } if (entrada.isClosed ()) {this.available.remove (entrada); Pool.Free (entrada, falso); } else {// Si obtiene una conexión válida, salga de la ruptura de bucle; }} // Si obtiene una conexión válida, salga if (entrada! = Null) {this.available.remove (entrada); this.leded.add (entrada); onreuse (entrada); entrada de devolución; } // Para probar aquí que no se obtuvo una conexión válida, debe generar un final int maxperroute = getmax (ruta); // El número máximo de conexiones correspondientes a cada ruta es configurable. Si excede, debe limpiar algunas conexiones a través de LRU final int excess = Math.max (0, Pool.GetAllocatedCount () + 1 - MaxperRoute); if (excess> 0) {for (int i = 0; i <excess; i ++) {final e lastUsed = proup.getLastused (); if (LastUse == null) {break; } LastUsed.Close (); this.available.remove (LastUsed); Pool.remove (Lastused); }} // El número de conexiones en el grupo de ruta actual no ha alcanzado el en línea if (prool.getAllocatedCount () <maxperRoute) {final int totalUseuseuseSe = this.lamed.size (); Final int Freecapacity = Math.max (this.Maxtotal - TotalUsed, 0); // juzga si el grupo de conexión excede la línea en línea. Si lo supera, debe limpiar algunas conexiones a través de LRU if (Freecapacity> 0) {final int totalAVailable = this.available.size (); // Si el número de conexiones gratuitas ya es mayor que el espacio disponible restante, debe limpiar la conexión gratuita si (totalVailable> freeCapacity - 1) {if (! This.available.isEmpty ()) {final E LastUsed = this.available.RemoVelast (); LastUsed.close (); RoutespecificPool final <t, c, e> otherpool = getpool (lastusued.getroute ()); OtherPool.Remove (Lastused); }} // Crear una conexión basada en la ruta final c conn = this.connfactory.create (ruta); // Ponga esta conexión en la "pequeña piscina" correspondiente a la entrada de ruta = Pool.add (Conn); // Ponga esta conexión en el "gran grupo" this.leded.add (entrada); entrada de devolución; }} // Para este fin, se demuestra que no hay una conexión válida desde el grupo de rutas obtenida, y cuando desea establecer una conexión usted mismo, el grupo de conexión de ruta actual ha alcanzado su valor máximo, es decir, ya hay una conexión en uso, pero no está disponible para el hilo actual boolean stitsing = false; intente {if (futuro.iscancelled ()) {tire nueva interrupciónxception ("operación interrumpida"); } // Ponga el futuro en la piscina de ruta esperando a Pool.Queue (futuro); // Ponga el futuro en la gran piscina de conexión esperando esto.pending.add (futuro); // Si espera la notificación del semáforo, el éxito es verdadero si (fecha límite! = NULL) {Success = this.condition.AwaitUtil (fecha límite); } else {this.condition.await (); éxito = verdadero; } if (futuro.iscancelled ()) {tire nueva interrupciónxception ("operación interrumpida"); }} Finalmente {// eliminar piscina.unqueue (futuro); this.pending.remove (futuro); } // Si la notificación de semáforo no se espera y la hora actual se ha agotado, el bucle se sale si (! Success && (fecha límite! = NULL && Deadline.getTime () <= System.CurrentTimEmillis ())) {rompe; }} // Al final, no se recibió ninguna notificación de semáforo y no se obtuvo ninguna conexión disponible, se lanzó una excepción. tirar nueva tiempo de espera ("tiempo de espera esperando la conexión"); } Finalmente {// libera el bloqueo en el grupo de conexión grande this.lock.unlock (); }}Hay varios puntos importantes en la lógica del código anterior:
Hasta ahora, el programa ha obtenido una instancia de CPOolEntry disponible o finalizado el programa lanzando una excepción.
4.3 httpClientConnection
HttpClientConnection protegido Leaseconnection (Future Final <CPoolEntry> Future, Tiempo largo final, Tunit Final TimeUnit) arroja interruptedException, ExecutionException, ConnectionPoolTimeOutException {Entrada final de CPoolEntry; Pruebe {// Obtenga la entrada CpoolEntry de la operación asíncrona Future <CpoolEntry> Entry = Future.get (Tiempo de espera, Tunit); if (entry == null || future.iscancelled ()) {throw new InterruptedException (); } Afirmss.check (entry.getConnection ()! = NULL, "Entrada de grupo sin conexión"); if (this.log.isDebugeNabled ()) {this.log.debug ("Conexión Sarcelado:" + Format (Entry) + FormatStats (Entry.getRoute ())); } // Obtenga un objeto proxy de cpoolEntry, y todas las operaciones se realizan utilizando el mismo httpclientconnection return cpoolproxy.newproxy (entrada); } Catch (Final TimeOutException ex) {lanzar una nueva conexión y tiempo de entradaxception ("Tiempo de espera esperando la conexión desde el grupo"); }} 5. ¿Cómo reutilizar las conexiones persistentes en httpclient?
En el capítulo anterior, vimos que HttpClient obtiene conexiones a través de las piscinas de conexión y las obtiene de la piscina cuando es necesario usar conexiones.
Correspondiente al tercer capítulo:
En el Capítulo 4, vimos cómo HttpClient maneja los problemas de 1 y 3, entonces, ¿cómo lidiamos con la segunda pregunta?
Es decir, ¿cómo determina HttpClient si una conexión debe cerrarse después de su uso, o debe colocarse en una piscina para que otros lo reutilicen? Mire el código de MainClientExec
// Enviar respuesta de conexión http = requestExeCutor.Execute (Solicitar, ManagedConn, context); // Defender si la conexión actual se reutiliza en función de la estrategia de reutilización si (ReusestrateGy.keepalive (Respuesta, context)) {// La conexión que debe reutilizarse, obtener el tiempo de tiempo de espera de conexión, en función del tiempo de espera en la larga duración de respuesta = KeepAlivestrate.getKeepAduredation (respuesta, contexto); if (this.log.isDebugeNabled ()) {String final S; // El tiempo de espera es el número de milisegundos, si no se establece, es -1, es decir, no hay tiempo de espera si (duración> 0) {s = "para" + duración + "" + timeUnit.milliseConds; } else {s = "indefinidamente"; } this.log.debug ("La conexión se puede mantener viva" + S); } // Establezca el tiempo de tiempo de espera. Cuando finaliza la solicitud, el administrador de conexión decidirá si se cerrará o volverá a colocarlo en el grupo en función del tiempo de tiempo de espera connholder.setValidfor (duración, timeUnit.milliseConds); // Regístrese en la conexión como reutilizable connholder.markreUsable (); } else {// Regístrese en la conexión como connholder no reutilizable.marknonreUsable (); }Se puede ver que después de que se produce una solicitud utilizando una conexión, hay una política de reintento de conexión para decidir si la conexión debe ser reutilizada. Si se reutiliza, se entregará al HttpClientConnectionManager después del final.
Entonces, ¿cuál es la lógica de la política de multiplexación de conexión?
clase pública defaultClientConnectionReUsestrategy extiende defaultConnectionReusErSErtgy {public static final DefaultClientConnectionReusEsStrateGy instancia = nueva defaultClientConnectionReusEsStrateGy (); @Override public boolean keepalive (respuesta final httpponse, contexto final httpContext) {// Obtener solicitud HTTPREQUEST final de la solicitud final de Httprequest final = (httprequest) context.getAttribute (httpcorecontext.http_request); if (request! = null) {// Obtener el encabezado final del encabezado de la conexión [] connheaders = request.getheaders (httpheaders.connection); if (connheaders.length! = 0) {tokenIterator final ti = new BasictoKenIderator (nuevo BasicheaderIterator (Connheaders, NULL)); while (ti.hasnext ()) {final de cadena token = ti.nexttoken (); // Si se incluye el encabezado de conexión: Cerrar, significa que la solicitud no tiene la intención de mantener la conexión, y la intención de la respuesta será ignorada. Este encabezado es la especificación de http/1.1 if (http.conn_close.equalsignorecase (token)) {return false; }}}}} // Use la estrategia de reutilización de la clase principal para devolver super.keepalive (respuesta, contexto); }}Eche un vistazo a la estrategia de reutilización de la clase matriz
if (canResponseHaveBody (request, respuesta)) {encabezado final [] clhs = respuesta.getheaders (http.content_len); // Si la longitud de contenido de la respuesta no se establece correctamente, la conexión no se reutilizará // porque para las conexiones persistentes, no es necesario restablecer la conexión entre las dos transmisiones, debe confirmar a qué solicitud se pertenece el contenido de contenido en función de la longitud de contenido a la longitud de contenido (CLH.l longitud ". = clhs [0]; intente {final int contentlen = integer.parseInt (clh.getValue ()); if (contentlen <0) {return false; }} catch (final NumberFormateException ex) {return false; }} else {return false; }} if (HeaderIterator.hasNext ()) {try {TokenIterator final ti = new BasictoKenIderator (HeaderIterator); boolean keepalive = false; while (ti.hasnext ()) {final de cadena token = ti.nexttoken (); // Si la respuesta tiene una conexión: Cerrar encabezado, se afirma explícitamente que se va a cerrar, y si (http.conn_close.equalsignorecase (token)) {return false; // Si la respuesta tiene una conexión: guarda de encabezado de alive, se afirma explícitamente que se debe persistir, se reutiliza} else if (http.conn_keep_alive.equalsignorecase (token)) {keepAlive = true; }} if (keepalive) {return true; }} catch (final parseException px) {return false; }} @Para resumir:
Como se puede ver en el código, su estrategia de implementación es consistente con las limitaciones de nuestras capas del protocolo del Capítulo 2 y el Capítulo 3.
6. Cómo limpiar las conexiones caducadas de httpclient
Antes de HttpClient 4.4, al reutilizar la conexión desde el grupo de conexión, verificará si expira y la limpiará si expira.
La versión posterior será diferente. Habrá un hilo separado para escanear las conexiones en el grupo de conexión. Después de descubrir que hay un último uso del tiempo que se ha establecido, se limpiará. El tiempo de espera predeterminado es de 2 segundos.
Public ClosableHttpClient Build () {// Si especifica que desea limpiar las conexiones expiradas e inactivas, se iniciará el hilo de limpieza. El valor predeterminado no se inicia si (EvicEx. maxidletimeUnit); SECREABLESCOPY.Add (new Closable () {@Override public void Close () lanza ioexception {ConnectionEvictor.shutdown (); try {ConnectionEvictor.AwaItTermination (1L, TimeUnit.Seconds);} Catch (final interrumpedException Interrumped) {horthinThrentThread (). // Ejecutar el hilo de limpieza ConnectionEvictor.start ();}Puede ver que cuando HttpClientBuilder se está construyendo, si se habilita la función de limpieza, se especifica, se creará un hilo de limpieza del grupo de conexión y ejecutarlo.
public IdleconnectionEvictor (final HttpClientConnectionManager ConnectionManager, final ThreadFactory ThreadFactory, Final Long Sleeptime, TimeUnit Final SleeptimeUnit, final Long Maxidletime, TimeUnit final MaxidletIMeUnit) {this.connectionManager = args.notnull (ConnectionManager, "Manager de conexión"); this.threadFactory = ThreadFactory! = NULL? ThreadFactory: nuevo defaultThreadFactory (); this.sleeptimems = sleeptimeunit! = nulo? SleeptimeUnit.Tomillis (Sleeptime): Sleeptime; this.maxidletimems = maxidletiMeUnit! = NULL? maxidlettimeUnit.tomillis (maxidletime): maxidletime; this.thread = this.threadfactory.newthread (new runnable () {@Override public void run () {try {// el bucle muerto, el hilo sigue ejecutando while (! Thread.CurrentThread (). ISInterrupted ()) {// ejecutar después de unos segundos de reposo, predeterminado a 10 segundos thread.sleep (sleeptimems); ConnectionManaer.CLoseExPiredConnections (); }Para resumir:
7. Resumen de este artículo
La investigación anterior se basa en la comprensión personal del código fuente HTTPClient. Si hay algún error, espero que todos dejen un mensaje para discutirlo activamente.
De acuerdo, lo anterior es todo el contenido de este artículo. Espero que el contenido de este artículo tenga cierto valor de referencia para el estudio o el trabajo de todos. Si tiene alguna pregunta, puede dejar un mensaje para comunicarse. Gracias por su apoyo a Wulin.com.