ArchiSteamFarm/IPC/Controllers/Api/NLogController.cs
/// <summary>
///     Fetches ASF log in realtime.
/// </summary>
/// <remarks>
///     This API endpoint requires a websocket connection.
/// </remarks>
Fetches ASF log in realtime.
Fetches ASF log in realtime .
[HttpGet]
	[ProducesResponseType(typeof(IEnumerable<GenericResponse<string>>), (int) HttpStatusCode.OK)]
	[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
	public async Task<ActionResult> NLogGet(CancellationToken cancellationToken) {
		if (HttpContext == null) {
			throw new InvalidOperationException(nameof(HttpContext));
		}
		if (!HttpContext.WebSockets.IsWebSocketRequest) {
			return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError!, $"{nameof(HttpContext.WebSockets.IsWebSocketRequest)}: {HttpContext.WebSockets.IsWebSocketRequest}")));
		}
		try {
			using WebSocket webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
			SemaphoreSlim sendSemaphore = new(1, 1);
			if (!ActiveLogWebSockets.TryAdd(webSocket, (sendSemaphore, cancellationToken))) {
				sendSemaphore.Dispose();
				return new EmptyResult();
			}
			try {
				if (ArchiKestrel.HistoryTarget != null) {
					await Task.WhenAll(ArchiKestrel.HistoryTarget.ArchivedMessages.Select(archivedMessage => PostLoggedMessageUpdate(webSocket, archivedMessage, sendSemaphore, cancellationToken))).ConfigureAwait(false);
				}
				while (webSocket.State == WebSocketState.Open) {
					WebSocketReceiveResult result = await webSocket.ReceiveAsync(Array.Empty<byte>(), cancellationToken).ConfigureAwait(false);
					if (result.MessageType != WebSocketMessageType.Close) {
						await webSocket.CloseAsync(WebSocketCloseStatus.InvalidMessageType, "You're not supposed to be sending any message but Close!", cancellationToken).ConfigureAwait(false);
						break;
					}
					await webSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", cancellationToken).ConfigureAwait(false);
					break;
				}
			} finally {
				if (ActiveLogWebSockets.TryRemove(webSocket, out (SemaphoreSlim Semaphore, CancellationToken CancellationToken) entry)) {
					await entry.Semaphore.WaitAsync(CancellationToken.None).ConfigureAwait(false); 
					entry.Semaphore.Dispose();
				}
			}
		} catch (ConnectionAbortedException e) {
			ASF.ArchiLogger.LogGenericDebuggingException(e);
		} catch (OperationCanceledException e) {
			ASF.ArchiLogger.LogGenericDebuggingException(e);
		} catch (WebSocketException e) {
			ASF.ArchiLogger.LogGenericDebuggingException(e);
		}
		return new EmptyResult();
	}
[ HttpGet ] [ ProducesResponseType ( typeof ( IEnumerable < GenericResponse < string > > ) , ( int ) HttpStatusCode . OK ) ] [ ProducesResponseType ( typeof ( GenericResponse ) , ( int ) HttpStatusCode . BadRequest ) ] public async Task < ActionResult > NLogGet ( CancellationToken cancellationToken ) { if ( HttpContext == null ) { throw new InvalidOperationException ( nameof ( HttpContext ) ) ; } if ( ! HttpContext . WebSockets . IsWebSocketRequest ) { return BadRequest ( new GenericResponse ( false , string . Format ( CultureInfo . CurrentCulture , Strings . WarningFailedWithError ! , $" { nameof ( HttpContext . WebSockets . IsWebSocketRequest ) } :  { HttpContext . WebSockets . IsWebSocketRequest } " ) ) ) ; } try { using WebSocket webSocket = await HttpContext . WebSockets . AcceptWebSocketAsync ( ) . ConfigureAwait ( false ) ; SemaphoreSlim sendSemaphore = new ( 1 , 1 ) ; if ( ! ActiveLogWebSockets . TryAdd ( webSocket , ( sendSemaphore , cancellationToken ) ) ) { sendSemaphore . Dispose ( ) ; return new EmptyResult ( ) ; } try { if ( ArchiKestrel . HistoryTarget != null ) { await Task . WhenAll ( ArchiKestrel . HistoryTarget . ArchivedMessages . Select ( archivedMessage => PostLoggedMessageUpdate ( webSocket , archivedMessage , sendSemaphore , cancellationToken ) ) ) . ConfigureAwait ( false ) ; } while ( webSocket . State == WebSocketState . Open ) { WebSocketReceiveResult result = await webSocket . ReceiveAsync ( Array . Empty < byte > ( ) , cancellationToken ) . ConfigureAwait ( false ) ; if ( result . MessageType != WebSocketMessageType . Close ) { await webSocket . CloseAsync ( WebSocketCloseStatus . InvalidMessageType , " " , cancellationToken ) . ConfigureAwait ( false ) ; break ; } await webSocket . CloseOutputAsync ( WebSocketCloseStatus . NormalClosure , " " , cancellationToken ) . ConfigureAwait ( false ) ; break ; } } finally { if ( ActiveLogWebSockets . TryRemove ( webSocket , out ( SemaphoreSlim Semaphore , CancellationToken CancellationToken ) entry ) ) { await entry . Semaphore . WaitAsync ( CancellationToken . None ) . ConfigureAwait ( false ) ; entry . Semaphore . Dispose ( ) ; } } } catch ( ConnectionAbortedException e ) { ASF . ArchiLogger . LogGenericDebuggingException ( e ) ; } catch ( OperationCanceledException e ) { ASF . ArchiLogger . LogGenericDebuggingException ( e ) ; } catch ( WebSocketException e ) { ASF . ArchiLogger . LogGenericDebuggingException ( e ) ; } return new EmptyResult ( ) ; }
Fetches ASF log in realtime.
Fetches ASF log in realtime .
// From now on we can return only EmptyResult as the response stream is already being used by existing websocket connection // Push initial history if available // ReSharper disable once AccessToDisposedClosure - we're waiting for completion with Task.WhenAll(), we're not going to exit using block // Ensure that our semaphore is truly closed by now
cancellationToken
CancellationToken
remarks
This API endpoint requires a websocket connection.
This API endpoint requires a websocket connection .