How does multithreading affect http keep-alive connection? - apache

var (
httpClient *http.Client
)
const (
MaxIdleConnections int = 20
RequestTimeout int = 5
)
// init HTTPClient
func init() {
client := &http.Client{
Transport: &http.Transport{
MaxIdleConnsPerHost: MaxIdleConnections,
},
Timeout: time.Duration(RequestTimeout) * time.Second,
}
return client
}
func makeRequest() {
var endPoint string = "https://localhost:8080/doSomething"
req, err := http.NewRequest("GET", ....)
response, err := httpClient.Do(req)
if err != nil && response == nil {
log.Fatalf("Error sending request to API endpoint. %+v", err)
} else {
// Close the connection to reuse it
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatalf("Couldn't parse response body. %+v", err)
}
log.Println("Response Body:", string(body))
}
}
I have the following code in Go. Go uses http-keep-alive connection. Thus, from my understanding, httpClient.Do(req) will not create a new connection, since golang uses default persistent connections.
From my understanding HTTP persistent connection makes one request at a time, ie the second request can only be made after first response. However if multiple threads call makeRequest() what will happen ? Will httpClient.Do(req) send another request even before previous one got a response ?
I assume server times-out any keep-alive connection made by client. If server were to timeout, then the next time httpClient.Do(req)is called, would it establish a new connection ?

an http.Client has a Transport to which it delegates a lot of the low-level details of making a request. You can change pretty much anything by giving your client a custom Transport. The rest of this answer will largely assume that you're using http.DefaultClient or at least a client with http.DefaultTransport.
When making a new request, if an idle connection to the appropriate server is available, the transport will use it.
If no idle connection is available (because there never was one, or because other goroutines are using them all, or because the server closed the connection, or there was some other error) then the transport will consider making a new connection, limited by MaxConnsPerHost (default: no limit). If MaxConnsPerHost would be exceeded, then the request will block until an existing request completes and a connection becomes available. Otherwise, a new connection will be made for this request.
On completion of a request, the client will cache the connection for later use (limited by MaxIdleConns and MaxIdleConnsPerHost; DefaultTransport has a limit of 100 idle connections globally, and no limit per-host).
Idle connections will be closed after IdleConnTimeout if they aren't used to make a request; for DefaultTransport the limit is 90 seconds.
All of which means that by default, Go will make enough connections to satisfy parallelism (up to certain limits which you can adjust) but it will also reuse keep-alive connections as much as possible by maintaining a pool of idle connections for some length of time.

It will not affect the http keep-alive connection, base on your code, you are using global httpClient, this will not create a new connection if called in multiple thread as you expected, Also it read the response.Body before it closed. If the provided response.Body is an io.Closer, it will closed after the request.

Related

Ratchet PHP server establishes connection, but Kotlin never receives acknowledgement

I have a ratchet server, that I try to access via Websocket. It is similar to the tutorial: logging when there is a new client or when it receives a message. The Ratchet server reports having successfully established a connection while the Kotlin client does not (the connection event in Kotlin is never fired). I am using the socket-io-java module v.2.0.1. The client shows a timeout after the specified timeout time, gets detached at the server and attaches again after a short while, just as it seems to think, the connection did not properly connect (because of a missing connection response?).
The successful connection confirmation gets reported to the client, if the client is a Websocket-Client in the JS-console of Chrome, but not to my Kotlin app. Even an Android emulator running on the same computer doesn´t get a response (So I think the problem is not wi-fi related).
The connection works fine with JS, completing the full handshake, but with an Android app it only reaches the server, but never the client again.
That´s my server code:
<?php
namespace agroSMS\Websockets;
use Ratchet\ConnectionInterface;
use Ratchet\MessageComponentInterface;
class SocketConnection implements MessageComponentInterface
{
protected \SplObjectStorage $clients;
public function __construct() {
$this->clients = new \SplObjectStorage;
}
function onOpen(ConnectionInterface $conn)
{
$this->clients->attach($conn);
error_log("New client attached");
}
function onClose(ConnectionInterface $conn)
{
$this->clients->detach($conn);
error_log("Client detached");
}
function onError(ConnectionInterface $conn, \Exception $e)
{
echo "An error has occurred: {$e->getMessage()}\n";
$conn->close();
}
function onMessage(ConnectionInterface $from, $msg)
{
error_log("Received message: $msg");
// TODO: Implement onMessage() method.
}
}
And the script that I run in the terminal:
<?php
use Ratchet\Server\IoServer;
use agroSMS\Websockets\SocketConnection;
use Ratchet\WebSocket\WsServer;
use Ratchet\Http\HttpServer;
require dirname(__DIR__) . '/vendor/autoload.php';
$server = IoServer::factory(
new HttpServer(
new WsServer(
new SocketConnection()
)
)
);
$server->run();
What I run in the browser for tests (returns "Connection established" in Chrome, but for some reason not in the Browser "Brave"):
var conn = new WebSocket('ws://<my-ip>:80');
conn.onopen = function(e) {
console.log("Connection established!");
};
conn.onmessage = function(e) {
console.log(e.data);
};
What my Kotlin-code looks like:
try {
val uri = URI.create("ws://<my-ip>:80")
val options = IO.Options.builder()
.setTimeout(60000)
.setTransports(arrayOf(WebSocket.NAME))
.build()
socket = IO.socket(uri, options)
socket.connect()
.on(Socket.EVENT_CONNECT) {
Log.d(TAG, "[INFO] Connection established")
socket.send(jsonObject)
}
.once(Socket.EVENT_CONNECT_ERROR) {
val itString = gson.toJson(it)
Log.d(TAG, itString)
}
}catch(e : Exception) {
Log.e(TAG, e.toString())
}
After a minute the Kotlin code logs a "timeout"-error, detaches from the server, and attaches again.
When I stop the script on the server, it then gives an error: "connection reset, websocket error" (which makes sense, but why doesn´t he get the connection in the first time?)
I also tried to "just" change the protocol to "wss" in the url, in case it might be the problem, even though my server doesn´t even work with SSL, but this just gave me another error:
[{"cause":{"bytesTransferred":0,"detailMessage":"Read timed out","stackTrace":[],"suppressedExceptions":[]},"detailMessage":"websocket error","stackTrace":[],"suppressedExceptions":[]}]
And the connection isn´t even established at the server. So this try has been more like a down-grade.
I went to the github page of socket.io-java-client to find a solution to my problem there and it turned out, the whole problem was, that I misunderstood a very important concept:
That socket.io uses Websockets doesn´t mean it is compatible with Websockets.
So speaking in clear words:
If you use socket.io at client side, you also need to use it at the server side and vice versa. Since socket.io sends a lot of meta data with its packets, a pure Websocket-server will accept their connection establishment, but his acknowledgement coming back will not be accepted by the socket.io client.
You have to go for either full socket.io or full pure Websockets.

HTTP2 PING frames over AWS ALB (gRPC keepalive ping)

I'm using AWS Application Load Balancer (ALB) to expose the ASP.NET Core gRPC services. The services are running in Fargate containers and expose unsecured HTTP ports. ALB terminates the outer TLS connection and forwards the unencrypted traffic to a target group based on the route. The gRPC application has several client streaming endpoints and the client can pause the streaming for several minutes. I know that there are HTTP2 PING frames, which can be used in such cases, to keep alive the connection that has no data transmission for some amount of time.
The gRPC server is configured to send HTTP2 pings every 20 seconds for keeping the connection alive. I tested this approach and it works, the ping frames went from the server and were acknowledged by the client.
But this approach fails when it comes to ALB. During the transmission pauses, I don't see any packages from the server behind the load balancer (I use Wireshark). Then after the timeout of 1 minute, the ALB resets the connection.
I tried to use client-sent HTTP2 pings as well. But the connection also resets in 1 minute and I have no evidence whether these ping packages actually reached the server behind the ALB.
I have an assumption that AWS ALB doesn't allow such packets to pass over it, but I didn't find any documentation that proves it.
ALB forwards requests based on HTTP protocol semantics, and not raw HTTP/2 frames. Therefore something like ping frames will only apply for one of the hops.
If you want an end to end ping, you could define a gRPC API which is performing the ping. For server to client you would be required to use a server side streaming APIs. But it might actually be preferrable to let the clients start the pings, to reduce the worker the server has to perform.
The AWS support team responded to my ticket and the short answer is ALB does not support the HTTP2 ping frames. They suggested increasing the value of idle timeout on the load balancer, but this solution may be not applicable in some cases.
As Matthias247 already mentioned, the possible workaround is to define a gRPC API for the purpose of doing a ping.
Since ALB does not support the HTTP2 ping frames. A straightforward way to solve it is to use a custom PING message.
I think you can get another new stream to send messages when the current stream is closed by ALB due to idle timeout (without messages within idle time)
When idle timeout of ALB, the RST_STREAM message with ErrCode=PROTOCOL_ERROR will be sent from ALB to client-side. The client could handle this error in sender and receiver and then get another new stream to send new messages to reuse the http2 connection.
Here are the sample codes with gRPC-go
conn, errD := grpc.Dial(ServerAddress,
grpc.WithTransportCredentials(cred),
grpc.WithConnectParams(grpc.ConnectParams{MinConnectTimeout: time.Duration(63) * time.Second}),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: time.Second * 20,
Timeout: time.Second * 3,
PermitWithoutStream: true,
}))
if errD != nil {
log.Fatalf("net.Connect err: %v", errD)
}
defer conn.Close()
grpcClient := protocol.NewChatClient(conn)
ctx := context.Background()
stream, errS := grpcClient.Stream(ctx, grpc.WaitForReady(true))
if errS != nil {
log.Fatalf("get BidirectionalHello stream err: %v", errS)
}
for i := 0; i < 200; i++ {
err := stream.Send(
// some message
})
if err != nil {
if err == io.EOF {
// get anothe stream to send new message on sender
stream, errS = grpcClient.Stream(ctx, grpc.WaitForReady(true))
if errS != nil {
log.Fatalf("get stream err: %v", errS)
}
} else if s, ok := status.FromError(err); ok {
switch s.Code() {
case codes.OK:
// noop
case codes.Unavailable, codes.Canceled, codes.DeadlineExceeded:
return
default:
return
}
}
}
go func() {
for {
res, errR := stream.Recv()
if errR != nil {
if errR == io.EOF {
log.Printf("stream recv err %+v \n", errR)
}
// get anothe stream to send new message on receiver
stream, errS = grpcClient.Stream(ctx, grpc.WaitForReady(true))
if errS != nil {
log.Fatalf("in recv to get stream err: %v", errS)
}
return
}
log.Printf("recv resp %+v", res)
}
}()
// over the idle timeout of alb (60 seconds)
time.Sleep(time.Duration(61) * time.Second)
}
To view the details of gRPC message, you could run it through GODEBUG=http2debug=2 go run main.go

Janus gateway videoroom cancels connection after 60 seconds

"peerConnection new connection state: connected"
{
"janus": "webrtcup",
"session_id": 3414770196795261,
"sender": 4530256184020316
}
{
"janus": "media",
"session_id": 3414770196795261,
"sender": 4530256184020316,
"type": "audio",
"receiving": true
}
... 1 minute passes
"peerConnection new connection state: disconnected"
{
"janus": "timeout",
"session_id": 3414770196795261
}
"peerConnection new connection state: failed"
See pastebin for the full logs.
I'm trying to join a videoroom on my Janus server. All requests seem to succeed, and my device shows a connected WebRTC status for around one minute before the connection is canceled because of a timeout.
The WebRTC connection breaking off seems to match up with the WebSocket connection to Janus' API breaking.
I tried adding a heartbeat WebSocket message every 10 seconds, but that didn't help. I'm
joining the room
receiving my local SDP plus candidates
configuring the room with said SDP
receiving an answer from janus
accepting that answer with my WebRTC peer connection.
Not sure what goes wrong here.
I also tried setting a STUN server inside the Janus config, to no avail. Same issue.
Added the server logs to the pastebin too.
RTFM: Janus' websocket connections require a keepalive every <60s.
An important aspect to point out is related to keep-alive messages for WebSockets Janus channels. A Janus session is kept alive as long as there's no inactivity for 60 seconds: if no messages have been received in that time frame, the session is torn down by the server. A normal activity on a session is usually enough to prevent that; for a more prolonged inactivity with respect to messaging, on plain HTTP the session is usually kept alive through the regular long poll requests, which act as activity as long as the session is concerned. This aid is obviously not possible when using WebSockets, where a single channel is used both for sending requests and receiving events and responses. For this reason, an ad-hoc message for keeping alive a Janus session should to be triggered on a regular basis. Link.
You need to send 'keepalive' message with same 'session_id'to keep the session going. Janus closes session after 60 seconds.
Look for the implementation: https://janus.conf.meetecho.com/docs/rest.html
Or do it my way: i do it every 30 seconds in a runnable handler.
private Handler mHandler;
private Runnable fireKeepAlive = new Runnable() {
#Override
public void run() {
String transactionId = getRandomStringId();
JSONObject request = new JSONObject();
try {
request.put("janus", "keepalive");
request.put("session_id", yourSessionId);
request.put("transaction", transactionId);
} catch (JSONException e) {
e.printStackTrace();
}
myWebSocketConnection.sendTextMessage(request.toString());
mHandler.postDelayed(fireKeepAlive, 30000);
}
};
Then in OnCreate()
mHandler = new Handler();
then call this where WebSocket connection Opens:
mHandler.post(fireKeepAlive);
be sure to remove callback on destroy
mHandler.removeCallbacks(fireKeepAlive);

HTTPClient intermittently locking up server

I have a .NET Core 2.2 app which has a controller acting as a proxy to my APIs.
JS makes a fetch to the proxy, proxy forwards call onto API's and returns response.
I am experiencing intermittent lock ups on the proxy app when its awaiting the response from the HttpClient. When this happens it locks up the entire server. No more requests will be processed.
According to the logs of the API that is being proxied to it is returning fine.
To reproduce this is i have to make 100+ requests in a loop on the client through the proxy. Then i have to reload the page multiple times, reloading it whilst the 100 requests are in flight. It usually takes around 5 hits before things start slowing down.
The proxy will lock up waiting for an awaited request to resolve. Sometimes it comes back after a 4 - 5 second delay, other times after a minuet. Most of the time i haven't waited longer then 10 min before giving up and killing the proxy.
I've distilled the code down to the following block that will reproduce the issue.
I believe im following best practices, its async all the way down, im using IHttpClientFactory to enable sharing of HttpClient instances, im implementing using where i believe it is required.
The implementation was based on this: https://github.com/aspnet/AspLabs/tree/master/src/Proxy
I'm hoping im making a rather obvious mistake that others with more experience can pin point!
Any help would be greatly appreciated.
namespace Controllers
{
[Route("/proxy")]
public class ProxyController : Controller
{
private readonly IHttpClientFactory _factory;
public ProxyController(IHttpClientFactory factory)
{
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
}
[HttpGet]
[Route("api")]
async public Task ProxyApi(CancellationToken requestAborted)
{
// Build API specific URI
var uri = new Uri("");
// Get headers frpm request
var headers = Request.Headers.ToDictionary(x => x.Key, y => y.Value);
headers.Add(HeaderNames.Authorization, $"Bearer {await HttpContext.GetTokenAsync("access_token")}");
// Build proxy request method. This is within a service
var message = new HttpRequestMessage();
foreach(var header in headers) {
message.Headers.Add(header.Key, header.Value.ToArray());
}
message.RequestUri = uri;
message.Headers.Host = uri.Authority;
message.Method = new HttpMethod(Request.Method);
requestAborted.ThrowIfCancellationRequested();
// Generate client and issue request
using(message)
using(var client = _factory.CreateClient())
// **Always hangs here when it does hang**
using(var result = await client.SendAsync(message, requestAborted).ConfigureAwait(false))
{
// Appy data from request onto response - Again this is within a service
Response.StatusCode = (int)result.StatusCode;
foreach (var header in result.Headers)
{
Response.Headers[header.Key] = header.Value.ToArray();
}
// SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response.
Response.Headers.Remove("transfer-encoding");
requestAborted.ThrowIfCancellationRequested();
using (var responseStream = await result.Content.ReadAsStreamAsync())
{
await responseStream.CopyToAsync(responseStream, 81920);
}
}
}
}
}
EDIT
So modified the code to remove the usings and return the proxied response directly as a string instead of streaming and still getting the same issues.
When running netstat i do see a lot of logs for the url of the proxied API.
4 rows mention the IP of the API being proxied to, probably about another 20 rows mentions the IP of the proxy site. Those numbers dont seem odd to me but i don't have much experience using netstat (first time ive ever fired it up).
Also i have left the proxy running for about 20 min. Its it technically still alive. Responses are coming back. Just taking a very long time between the API being proxied to returning data and the HttpClient resolving. However it wont service any new requests, they just sit there hanging.

IllegalArgumentException: "Auth scheme may not be null" in CloseableHttpAsyncClient

I'm running some asynchronous GET requests using a proxy with authentication. When doing HTTPS requests, I'm always running into an exception after 2 successful asyncronous requests:
java.lang.IllegalArgumentException: Auth scheme may not be null
When executing the GET requests without a proxy, or using http instead of https, the exception never occurred.
Example from Apache HttpAsyncClient Examples
HttpHost proxy = new HttpHost("proxyname", 3128);
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(proxy), new UsernamePasswordCredentials("proxyuser", "proxypass"));
CloseableHttpAsyncClient httpClient = HttpAsyncClients.custom().setDefaultCredentialsProvider(credsProvider).build();
httpClient.start();
RequestConfig config = RequestConfig.custom().setProxy(proxy).build();
for (int i = 0; i < 3; i++) {
HttpGet httpGet = new HttpGet(url);
httpGet.setConfig(config);
httpClient.execute(httpGet, new FutureCallback<HttpResponse>() {
public void failed(Exception ex) {
ex.printStackTrace(); // Exception occures here afther 2nd iteration
}
public void completed(HttpResponse result) {
// works for the first and second iteration
}
public void cancelled() {
}
});
}
httpClient.close();
If I run the code above with 'http://httpbin.org/get', there is no exception, but if I run it with 'https://httpbin.org/get', I get the following exception after 2 successful requests:
java.lang.IllegalArgumentException: Auth scheme may not be null
at org.apache.http.util.Args.notNull(Args.java:54)
at org.apache.http.impl.client.AuthenticationStrategyImpl.authSucceeded(AuthenticationStrategyImpl.java:215)
at org.apache.http.impl.client.ProxyAuthenticationStrategy.authSucceeded(ProxyAuthenticationStrategy.java:44)
at org.apache.http.impl.auth.HttpAuthenticator.isAuthenticationRequested(HttpAuthenticator.java:88)
at org.apache.http.impl.nio.client.MainClientExec.needAuthentication(MainClientExec.java:629)
at org.apache.http.impl.nio.client.MainClientExec.handleResponse(MainClientExec.java:569)
at org.apache.http.impl.nio.client.MainClientExec.responseReceived(MainClientExec.java:309)
at org.apache.http.impl.nio.client.DefaultClientExchangeHandlerImpl.responseReceived(DefaultClientExchangeHandlerImpl.java:151)
at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.responseReceived(HttpAsyncRequestExecutor.java:315)
at org.apache.http.impl.nio.DefaultNHttpClientConnection.consumeInput(DefaultNHttpClientConnection.java:255)
at org.apache.http.impl.nio.client.InternalIODispatch.onInputReady(InternalIODispatch.java:81)
at org.apache.http.impl.nio.client.InternalIODispatch.onInputReady(InternalIODispatch.java:39)
at org.apache.http.impl.nio.reactor.AbstractIODispatch.inputReady(AbstractIODispatch.java:121)
at org.apache.http.impl.nio.reactor.BaseIOReactor.readable(BaseIOReactor.java:162)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvent(AbstractIOReactor.java:337)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvents(AbstractIOReactor.java:315)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:276)
at org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:104)
at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:591)
at java.lang.Thread.run(Thread.java:748)
Note: I'm using httpasyncclient 4.1.4
If this is the exact code you have been executing then the problem is quite apparent. Welcome to the world of even-driven programming.
Essentially what happens is the following:
The client initiates 3 message exchanges by submitting 3 requests to the client execution pipeline in a tight loop
3 message exchanges get queued up for execution
The loop exits
Client shutdown is initiated
Now the client is racing to execute 3 initiated message exchanges and to shut itself down at the same time
If one is lucky and the target server is fast enough one might get all 3 exchanges before the client shuts down its i/o event processing threads
If unlucky or when the request execution is relatively slow, for instance due, to the use of TLS transport security, some of message exchanges might get terminated in the middle of the process. This is the reason you are seeing the failure when using https scheme but not http.