public static void AddRin(this IServiceCollection services, Action <RinOptions> configure = null) { var options = new RinOptions(); configure?.Invoke(options); services.AddHttpContextAccessor(); var eventBus = new MessageEventBus <RequestEventMessage>(); var eventBusStoreBody = new MessageEventBus <StoreBodyEventMessage>(); var transformerSet = new BodyDataTransformerSet( new BodyDataTransformerPipeline(options.Inspector.RequestBodyDataTransformers), new BodyDataTransformerPipeline(options.Inspector.ResponseBodyDataTransformers) ); // Other services services.AddSingleton <BodyDataTransformerSet>(transformerSet); services.AddSingleton <IRecordStorage>(options.RequestRecorder.StorageFactory); services.AddSingleton <IMessageEventBus <RequestEventMessage> >(eventBus); services.AddSingleton <IMessageEventBus <StoreBodyEventMessage> >(eventBusStoreBody); services.AddSingleton <RinOptions>(options); services.AddSingleton <RinChannel>(); // IMessageSubscriber<RequestEventMessage> services services.AddSingleton <IMessageSubscriber <RequestEventMessage> >(x => new Rin.Hubs.RinCoreHub.MessageSubscriber(x.GetRequiredService <RinChannel>())); services.AddTransient <IResourceProvider, EmbeddedZipResourceProvider>(); }
private async Task PostprocessAsync(HttpContext context, RinOptions options, HttpRequestRecord record) { var request = context.Request; var response = context.Response; record.Processing.Complete(); record.ResponseStatusCode = response.StatusCode; record.ResponseHeaders = response.Headers.ToDictionary(k => k.Key, v => v.Value); if (options.RequestRecorder.EnableBodyCapturing) { var memoryStreamRequestBody = new MemoryStream(); request.Body.Position = 0; // rewind the stream to head await request.Body.CopyToAsync(memoryStreamRequestBody); var feature = context.Features.Get <IRinRequestRecordingFeature>(); await _eventBusStoreBody.PostAsync(new StoreBodyEventMessage(StoreBodyEvent.Request, record.Id, memoryStreamRequestBody.ToArray())); await _eventBusStoreBody.PostAsync(new StoreBodyEventMessage(StoreBodyEvent.Response, record.Id, feature.ResponseDataStream.GetCapturedData())); } var exceptionFeature = context.Features.Get <IExceptionHandlerFeature>(); if (exceptionFeature != null) { record.Exception = new ExceptionData(exceptionFeature.Error); } }
public static IRinBuilder AddRin(this IServiceCollection services, Action <RinOptions>?configure = null) { var options = new RinOptions(); configure?.Invoke(options); services.AddHttpContextAccessor(); // Other services services.AddSingleton <IRinRequestRecordingFeatureAccessor>(new RinRequestRecordingFeatureAccessor()); services.AddSingleton <BodyDataTransformerSet>(serviceProvider => { var requestTransformers = serviceProvider.GetServices <IRequestBodyDataTransformer>(); var responseTransformers = serviceProvider.GetServices <IResponseBodyDataTransformer>(); var transformers = serviceProvider.GetServices <IBodyDataTransformer>(); return(new BodyDataTransformerSet(new BodyDataTransformerPipeline(requestTransformers.Concat(transformers)), new BodyDataTransformerPipeline(responseTransformers.Concat(transformers)))); }); services.TryAddSingleton <IRecordStorage, InMemoryRecordStorage>(); services.AddSingleton <IMessageEventBus <RequestEventMessage>, MessageEventBus <RequestEventMessage> >(); services.AddSingleton <IMessageEventBus <StoreBodyEventMessage>, MessageEventBus <StoreBodyEventMessage> >(); services.AddSingleton <RinOptions>(options); services.AddSingleton <RinChannel>(); // IMessageSubscriber<RequestEventMessage> services services.AddSingleton <IMessageSubscriber <RequestEventMessage> >(x => new Rin.Hubs.RinCoreHub.MessageSubscriber(x.GetRequiredService <RinChannel>())); services.AddTransient <IResourceProvider, EmbeddedZipResourceProvider>(); return(new RinBuilder(services)); }
public async Task InvokeAsync(HttpContext context, RinOptions options) { var request = context.Request; var response = context.Response; if (request.Path.StartsWithSegments(options.Inspector.MountPath) || (options.RequestRecorder.Excludes.Any(x => x.Invoke(request)))) { await _next(context); return; } // Prepare AsyncLocals var timelineRoot = TimelineScope.Prepare(); _recordingFeatureAccessor.SetValue(null); HttpRequestRecord?record = default; try { record = await PreprocessAsync(context, options, timelineRoot); } catch (Exception ex) { _logger.LogError(ex, "Unhandled Exception was thrown until pre-processing"); } try { await _next(context); } catch (Exception ex) { if (record != null) { record.Exception = new ExceptionData(ex); } throw; } finally { try { if (record != null) { await PostprocessAsync(context, options, record); } } catch (Exception ex) { _logger.LogError(ex, "Unhandled Exception was thrown until post-processing"); } } }
private async Task <HttpRequestRecord> PreprocessAsync(HttpContext context, RinOptions options, ITimelineScope timelineRoot) { var request = context.Request; var response = context.Response; var record = new HttpRequestRecord() { Id = Guid.NewGuid().ToString(), IsHttps = request.IsHttps, Host = request.Host.Value, QueryString = request.QueryString.Value, Path = request.Path, Method = request.Method, RequestReceivedAt = DateTimeOffset.Now, RequestHeaders = request.Headers.ToDictionary(k => k.Key, v => v.Value), RemoteIpAddress = request.HttpContext.Connection.RemoteIpAddress, Timeline = timelineRoot, }; // Set Rin recorder feature. var feature = new RinRequestRecordingFeature(record);; _recordingFeatureAccessor.SetValue(feature); context.Features.Set <IRinRequestRecordingFeature>(feature); await _eventBus.PostAsync(new RequestEventMessage(EventSourceName, record, RequestEvent.BeginRequest)); // Set a current Rin request ID to response header. context.Response.Headers.Add("X-Rin-Request-Id", record.Id); if (options.RequestRecorder.EnableBodyCapturing) { context.EnableResponseDataCapturing(); request.EnableBuffering(); } response.OnStarting(OnStarting, record); response.OnCompleted(OnCompleted, record); // Execute pipeline middlewares. record.Processing = TimelineScope.Create("Processing", TimelineEventCategory.AspNetCoreCommon); return(record); }
public RedisRecordStorage(IOptions <RedisRecordStorageOptions> options, IOptions <RinOptions> rinOptions, IMessageEventBus <RequestEventMessage> eventBus) { _options = options.Value; _rinOptions = rinOptions.Value; _eventBus = eventBus; _redisConnection = ConnectionMultiplexer.Connect(_options.ConnectionConfiguration); _redis = _redisConnection.GetDatabase(_options.Database); _redisSubscriber = _redisConnection.GetSubscriber(); _redisSubscriber.Subscribe(GetRedisKey(RedisSubscriptionKey), (channel, value) => { var message = Deserialize <RequestEventMessage>(value); // Ignore a messages from this storage. if (message.EventSource == _eventSourceKey) { return; } _eventBus.PostAsync(message); }); }
public RinHelperService(IHttpContextAccessor httpContextAccessor, RinOptions options) { _httpContextAccessor = httpContextAccessor; _rinOptions = options; }