Jump to content

ApiClient; Is there a "close connection" call?


Sludge Vohaul

Recommended Posts

Sludge Vohaul

Hi,

 

I was wondering whether there is a way to tell the server that a client is done with his work in a "polite" way.

So far, my client establishes a connection to the server, eventually logs in and does his work, logs out, and then terminates.

 

The final HTTP request sent, is the Logout (POST) request, which also transmits the HTTP header "Connection: Keep-Alive".

I am either missing something, but IMO the usual pattern for clients is to log in, do some work and log out.

Why would I (from the client's point of view) want to keep the connection alive after logging out?

 

I would expect the ApiClient's logout-functionality to send a "Connection: Close" header instead, so that the usual connection termination handshake can take place, instead of sending a reset (RST) when the client terminates.

 

So what exactly am I supposed to do after calling a (pseudo-code) "client.Logout()", when the next step I want to do is terminating the application?

Link to comment
Share on other sites

there isn't actually an active connection unless you are using the websocket feature, in which case you can just close that.

 

you don't need to do anything when terminating the application. you can call logout if you wish, but that will force the user to login again.

Link to comment
Share on other sites

Sludge Vohaul

What is the mentioned "websocket feature"?

 

Maybe we're talking about two different kinds of "clients" - I guess your definition for a client is something that is connected to the server, permanently waiting for some user interaction.

My definition for a client is something which comes, does some work, and disappears.

 

In my case I'd wish to have a way to gracefully end the connection to the server (w/o having the TCP-stack send a RST because the socket has been closed due to end of the process).

My app basically calls ApiClient.Logout() at the end, which keeps the connection open, until the application terminates. The wireshark dump: 

 

59c992f9ed18e_ScreenShot20170926at005955

 

IMO there should be a way to initiate a graceful connection close in the ApiClient API, something like ApiClient.CloseConnection() which would lead to the common client [FIN, ACK], server [ACK], server [FIN, ACK], client [ACK] handshake.

Link to comment
Share on other sites

There's no explicit open connection in the apiclient library, but it's possible that the http client that its' using might be keeping a connection open. you could look into that and how it could be updated to close.

Link to comment
Share on other sites

Sludge Vohaul

@@Luke
 
Well, that's a two step story.

First, the method ApplyHeaders() in HttpWebRequestClient.cs would need to be made aware of restricted headers, something like this:
 

private void ApplyHeaders(HttpHeaders headers, HttpWebRequest request)
{
    foreach (var header in headers)
    {
        // ============ Old code ============
        //request.Headers[header.Key] = header.Value;
 
        // ============ New code ============
        if (WebHeaderCollection.IsRestricted(header.Key))
        {
            switch (header.Key)
            {
                case "Connection": request.Connection = header.Value;
                    break;
                default:
                    throw new SomeException("Unhandled restricted header", header.key);
            }
        }
        else
        {
            request.Headers[header.Key] = header.Value;
        }
    }
 
    if (!string.IsNullOrEmpty(headers.AuthorizationScheme))
    {
        var val = string.Format("{0} {1}", headers.AuthorizationScheme, headers.AuthorizationParameter);
        request.Headers["X-Emby-Authorization"] = val;
    }
}

Second, something is needed to get the "Connection=Close" HTTP header into the game.

From my limited understanding of the API, the ApiClient.cs method PostAsync() could get a new optional parameter indicating whether the connection should be kept (default) or closed. Something like 

public async Task<T> PostAsync<T>(string url, Dictionary<string, string> args, bool isKeepAlive = true, CancellationToken cancellationToken = default(CancellationToken))
            where T : class
{
    url = AddDataFormat(url);
 
    // Create the post body
    var strings = args.Keys.Select(key => string.Format("{0}={1}", key, args[key]));
    var postContent = string.Join("&", strings.ToArray());
 
    const string contentType = "application/x-www-form-urlencoded";
 
    // ============ New code ============
    if (isKeepAlive == false)
    {
        if (HttpHeaders.ContainsKey("Connection"))
        {
            HttpHeaders["Connection"] = "Close";
        }
        else
        {
            HttpHeaders.Add("Connection", "Close");
        }
    }
    // ============ End of new code ============ 
 
    using (var stream = await SendAsync(new HttpRequest
    {
        Url = url,
        CancellationToken = cancellationToken,
        RequestHeaders = HttpHeaders,
        Method = "POST",
        RequestContentType = contentType,
        RequestContent = postContent
    }).ConfigureAwait(false))
    {
        return DeserializeFromStream<T>(stream);
    }
}

The isKeepAlive flag would eventually add the restricted HTTP header "Connection" with value "Close" to HttpHeaders, and the header's value would then be set through the "appropriate property" in HttpWebRequestClient.cs

 

This way the server would start the connection close handshake (server [FIN, ACK], client [ACK], client [FIN, ACK], server [ACK]).

 

But that's pure theory, I haven't compiled the server code with these changes :)

 

Edit:

Ok, ApiClient.cs method Logout() should also have the optional parameter isKeepAlive ...

public async Task Logout(bool isKeepAlive = true)
{
    try
    {
        var url = GetApiUrl("Sessions/Logout");
 
        // ============ Old code ============
        //await PostAsync<EmptyRequestResult>(url, new Dictionary<string, string>(), CancellationToken.None);
 
        // ============ New code ============
        await PostAsync<EmptyRequestResult>(url, new Dictionary<string, string>(), isKeepAlive, CancellationToken.None);
    }
    catch (Exception ex)
    {
        Logger.ErrorException("Error logging out", ex);
    }
 
    ClearAuthenticationInfo();
}

I haven't checked who (if anyone at all) updates HttpHeaders, so it might be necessary to change the "Connection" header back to "keep-alive" somewhere...

Edited by Sludge Vohaul
  • Like 1
Link to comment
Share on other sites

Wow.

 

Just out of curiously, if you have a client connect to do work and then disconnect. Would that entail as specific account for that 'user' with hard coded authentication creds?

 

What kind of work would a client like that be able to do that a plugin couldn't handle?

 

Or is it that your interested in a way to disconnect in a nicer way?

Link to comment
Share on other sites

Sludge Vohaul

@@chef
 
Hi,
 

<snip> Would that entail as specific account for that 'user' with hard coded authentication creds?

Not sure whether I understood your question correctly, but "my" client is short-living CLI tool, which gathers any Emby login credentials (username/password) from the invoking person upon startup, eventually successfully connects to the Emby server, does his job and disconnects (it's job is to import metadata from a Plex library to Emby).
It's not a client sitting there and waiting for any user interaction (like e.g. the WebUI).

EDIT (see #8 - This is not true) -> 
I am aware of the fact, that the access token is not session (or whatever) based, but user based, meaning that a logout initiated by my client logs out all other clients this particular user is currently logged in with (WebUI, AppleTV, ...).
Have not thought about how to solve this yet...

<- EDIT
So, nothing is hard coded, and there is no specific user account, if that answers your question.
 

What kind of work would a client like that be able to do that a plugin couldn't handle?


A plugin could do the same job as well, but why would I want to install a plugin for anything I do exactly _once_ (migration from Plex to Emby)?
 

Or is it that your interested in a way to disconnect in a nicer way?


Well, actually yes(no) :)
I stumbled upon the whole issue while comparing my tool's Wireshark dumps to the WebUI's dumps and saw the keep-alive Connection headers in the logout POSTs.
The Emby API assumes that a client will be connected forever, which is wrong. There needs to be a way to allow clients to end an active connection.

Thanks,
sv

Edited by Sludge Vohaul
  • Like 1
Link to comment
Share on other sites

@@chef

 

I am aware of the fact, that the access token is not session (or whatever) based, but user based, meaning that a logout initiated by my client logs out all other clients this particular user is currently logged in with (WebUI, AppleTV, ...).

 

That's not true.

Link to comment
Share on other sites

Sludge Vohaul

Yep, sorry.

 

Might be wrong what I wrote - I always took the token from the WebUI session during testing.

 

Will correct and update...

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...