public CachingProxyV3NancyModuleTest() { metaCacheProvider = new Mock <IPackageMetadataCache>(MockBehavior.Strict); client = new Mock <IHttpClient>(MockBehavior.Strict); originalResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new ByteArrayContent(Encoding.UTF8.GetBytes(originalResponseContent)) }; originalResponse.Content.Headers.Add("Content-Type", "application/json"); client.Setup(c => c.SendAsync(It.IsAny <HttpRequestMessage>())) .ReturnsAsync(originalResponse); interceptor = new GenericV3JsonInterceptor(); replacementsProvider = new Mock <IUrlReplacementsProvider>(MockBehavior.Strict); replacementsProvider.Setup(p => p.GetReplacements(It.IsAny <string>())).Returns(new Dictionary <string, string>()); cacheProvider = new Mock <INupkgCacheProvider>(MockBehavior.Strict); tx = new Mock <INupkgCacheTransaction>(MockBehavior.Strict); cacheProvider.Setup(c => c.OpenTransaction()).Returns(tx.Object); tx.Setup(t => t.TryGet(It.IsAny <string>(), It.IsAny <string>())).Returns(null as byte[]); tx.Setup(t => t.Dispose()); tx.Setup(t => t.Insert(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <byte[]>())); var bootstrapper = new TestBootstrapper(typeof(CachingProxyV3NancyModule), b => { b.RegisterInstance(client.Object).As <IHttpClient>(); b.RegisterInstance(interceptor).As <IV3JsonInterceptor>(); b.RegisterInstance(replacementsProvider.Object).As <IUrlReplacementsProvider>(); b.RegisterInstance(cacheProvider.Object).As <INupkgCacheProvider>(); b.RegisterInstance(metaCacheProvider.Object).As <IPackageMetadataCache>(); }); this.browser = new Browser(bootstrapper, ctx => { ctx.HostName("testhost"); }); }
public CachingProxyV3NancyModule(IHttpClient client, IV3JsonInterceptor interceptor, IUrlReplacementsProvider replacementsProvider, INupkgCacheProvider nupkgCache, IPackageMetadataCache metadataCache) : base(basePath) { this.metadataCache = metadataCache; this.client = client; this.interceptor = interceptor; this.replacementsProvider = replacementsProvider; this.OnError.AddItemToEndOfPipeline(HandleError); base.Get <Response>("/v3/registration3/{package}/index.json", async args => { string package = args.package; byte[] bytes = this.metadataCache.TryGet(package); if (bytes != null) { _log.DebugFormat("Cache hit. Serving {0} from cache", Request.Url); return(new Response() { StatusCode = Nancy.HttpStatusCode.OK, ContentType = "application/json", Contents = netStream => { try { netStream.Write(bytes, 0, bytes.Length); netStream.Flush(); _log.DebugFormat("Served {0} bytes from cache", bytes.Length); } catch (Exception ex) { _log.Error("Something went wrong when serving query from cache", ex); throw new Exception("Serving query from cache failed", ex); } } }); } string myV3Url = this.GetServiceUrl().AbsoluteUri; Dictionary <string, string> replacements = replacementsProvider.GetReplacements(myV3Url); var request = new HttpRequestMessage() { RequestUri = replacementsProvider.GetOriginUri(Request.Url), Method = HttpMethod.Get, }; request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); _log.DebugFormat("Cache miss. Proxying {0} to {1}", Request.Url, request.RequestUri); var timestamp = DateTimeOffset.UtcNow; var originalResponse = await client.SendAsync(request); return(new DisposingResponse(originalResponse) { StatusCode = (Nancy.HttpStatusCode)(int) originalResponse.StatusCode, ContentType = originalResponse.Content.Headers.ContentType.MediaType, Contents = netStream => { Stream originalStream = null; try { originalStream = originalResponse.Content.ReadAsStreamAsync().Result; if (originalResponse.Content.Headers.ContentEncoding.Contains("gzip")) { originalStream = new GZipStream(originalStream, CompressionMode.Decompress); } using (var ms = new MemoryStream()) { interceptor.Intercept(replacements, originalStream, ms); bytes = ms.ToArray(); this.metadataCache.Insert(package, timestamp, bytes); netStream.Write(bytes, 0, bytes.Length); netStream.Flush(); } } catch (Exception ex) { _log.Error("Something went wrong when intercepting origin response", ex); throw new Exception("Intercepting origins response failed", ex); } finally { if (originalStream != null) { originalStream.Dispose(); } originalResponse.Dispose(); } } }); }); base.Get <Response>("/{path*}", async args => { string myV3Url = this.GetServiceUrl().AbsoluteUri; Dictionary <string, string> replacements = replacementsProvider.GetReplacements(myV3Url); var request = new HttpRequestMessage() { RequestUri = replacementsProvider.GetOriginUri(this.Request.Url), Method = HttpMethod.Get, }; request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); _log.DebugFormat("Proxying {0} to {1}", this.Request.Url, request.RequestUri); var originalResponse = await client.SendAsync(request); if (!(200 <= (int)originalResponse.StatusCode && (int)originalResponse.StatusCode < 300)) { _log.WarnFormat("Origin server {0} response status is {1}", request.RequestUri, originalResponse.StatusCode); } return(new DisposingResponse(originalResponse) { StatusCode = (Nancy.HttpStatusCode)(int) originalResponse.StatusCode, ContentType = originalResponse.Content.Headers.ContentType.MediaType, Contents = netStream => { Stream originalStream = null; try { originalStream = originalResponse.Content.ReadAsStreamAsync().Result; if (originalResponse.Content.Headers.ContentEncoding.Contains("gzip")) { originalStream = new GZipStream(originalStream, CompressionMode.Decompress); } if (new MediaRange("application/json").Matches(new MediaRange(originalResponse.Content.Headers.ContentType.MediaType))) { interceptor.Intercept(replacements, originalStream, netStream); } else { originalStream.CopyTo(netStream); } } catch (Exception ex) { _log.Error("Something went wrong when intercepting origin response", ex); throw new Exception("Intercepting origins response failed", ex); } finally { if (originalStream != null) { originalStream.Dispose(); } originalResponse.Dispose(); } } }); }); base.Get <Response>("/v3-flatcontainer/{package}/{version}/{filename}", async args => { string package = args.package; string version = args.version; string path = package + "/" + version + "/" + args.filename; byte[] hit; using (var tx = nupkgCache.OpenTransaction()) { hit = tx.TryGet(args.package, args.version); } if (hit == null) { // cache miss var request = new HttpRequestMessage() { RequestUri = replacementsProvider.GetOriginUri(this.Request.Url), Method = HttpMethod.Get, }; request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream")); _log.DebugFormat("Cache miss. Proxying {0} to {1}", this.Request.Url, request.RequestUri); var originalResponse = await client.SendAsync(request); return(new DisposingResponse(originalResponse) { ContentType = originalResponse.Content.Headers.ContentType.MediaType, Contents = netStream => { Stream originalStream = null; try { using (var cacheTx = nupkgCache.OpenTransaction()) { originalStream = originalResponse.Content.ReadAsStreamAsync().Result; using (var ms = new MemoryStream((int)originalResponse.Content.Headers.ContentLength.GetValueOrDefault(4096))) { originalStream.CopyTo(ms); byte[] value = ms.ToArray(); var writing = netStream.WriteAsync(value, 0, value.Length); try { cacheTx.Insert(package, version, value); } catch (Exception cacheError) { _log.Error("Failed to insert package to cache", cacheError); } finally { writing.Wait(); netStream.Flush(); } } } } catch (Exception ex) { _log.Error("Something went wrong when serving nupkg", ex); throw new Exception("Serving nupkg failed", ex); } finally { if (originalStream != null) { originalStream.Dispose(); } originalResponse.Dispose(); } } }); } else { // cache hit, return from cache _log.DebugFormat("Cache hit. Serving {0} from cache", this.Request.Url); return(new DisposingResponse() { ContentType = "application/octet-stream", Contents = netStream => { try { netStream.Write(hit, 0, hit.Length); netStream.Flush(); } catch (Exception ex) { _log.Error("Something went wrong when serving nupkg from cache", ex); throw new Exception("Serving nupkg from cache failed", ex); } } }); } }); base.Head <object>(@"^(.*)$", new Func <dynamic, object>(ThrowNotSupported)); base.Post <object>(@"^(.*)$", new Func <dynamic, object>(ThrowNotSupported)); base.Put <object>(@"^(.*)$", new Func <dynamic, object>(ThrowNotSupported)); base.Delete <object>(@"^(.*)$", new Func <dynamic, object>(ThrowNotSupported)); }