public static bool TryParse (string input, out EntityTagHeaderValue parsedValue) { parsedValue = null; var lexer = new Lexer (input); var t = lexer.Scan (); bool is_weak = false; if (t == Token.Type.Token) { if (lexer.GetStringValue (t) != "W" || lexer.PeekChar () != '/') return false; is_weak = true; lexer.EatChar (); t = lexer.Scan (); } if (t != Token.Type.QuotedString) return false; if (lexer.Scan () != Token.Type.End) return false; parsedValue = new EntityTagHeaderValue (); parsedValue.Tag = lexer.GetStringValue (t); parsedValue.IsWeak = is_weak; return true; }
protected virtual (RangeItemHeaderValue range, long rangeLength, bool serveBody) SetHeadersAndLog( ActionContext context, FileResult result, long?fileLength, bool enableRangeProcessing, DateTimeOffset?lastModified = null, EntityTagHeaderValue etag = null) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (result == null) { throw new ArgumentNullException(nameof(result)); } SetContentType(context, result); SetContentDispositionHeader(context, result); var request = context.HttpContext.Request; var httpRequestHeaders = request.GetTypedHeaders(); var preconditionState = GetPreconditionState(httpRequestHeaders, lastModified, etag); var response = context.HttpContext.Response; SetLastModifiedAndEtagHeaders(response, lastModified, etag); // Short circuit if the preconditional headers process to 304 (NotModified) or 412 (PreconditionFailed) if (preconditionState == PreconditionState.NotModified) { response.StatusCode = StatusCodes.Status304NotModified; return(range : null, rangeLength : 0, serveBody : false); } else if (preconditionState == PreconditionState.PreconditionFailed) { response.StatusCode = StatusCodes.Status412PreconditionFailed; return(range : null, rangeLength : 0, serveBody : false); } if (fileLength.HasValue) { // Assuming the request is not a range request, and the response body is not empty, the Content-Length header is set to // the length of the entire file. // If the request is a valid range request, this header is overwritten with the length of the range as part of the // range processing (see method SetContentLength). response.ContentLength = fileLength.Value; // Handle range request if (enableRangeProcessing) { SetAcceptRangeHeader(response); // If the request method is HEAD or GET, PreconditionState is Unspecified or ShouldProcess, and IfRange header is valid, // range should be processed and Range headers should be set if ((HttpMethods.IsHead(request.Method) || HttpMethods.IsGet(request.Method)) && (preconditionState == PreconditionState.Unspecified || preconditionState == PreconditionState.ShouldProcess) && (IfRangeValid(httpRequestHeaders, lastModified, etag))) { return(SetRangeHeaders(context, httpRequestHeaders, fileLength.Value)); } } else { Logger.NotEnabledForRangeProcessing(); } } return(range : null, rangeLength : 0, serveBody : !HttpMethods.IsHead(request.Method)); }
// Internal for testing internal PreconditionState GetPreconditionState( RequestHeaders httpRequestHeaders, DateTimeOffset?lastModified = null, EntityTagHeaderValue etag = null) { var ifMatchState = PreconditionState.Unspecified; var ifNoneMatchState = PreconditionState.Unspecified; var ifModifiedSinceState = PreconditionState.Unspecified; var ifUnmodifiedSinceState = PreconditionState.Unspecified; // 14.24 If-Match var ifMatch = httpRequestHeaders.IfMatch; if (etag != null) { ifMatchState = GetEtagMatchState( etagHeader: ifMatch, etag: etag, matchFoundState: PreconditionState.ShouldProcess, matchNotFoundState: PreconditionState.PreconditionFailed); if (ifMatchState == PreconditionState.PreconditionFailed) { Logger.IfMatchPreconditionFailed(etag); } } // 14.26 If-None-Match var ifNoneMatch = httpRequestHeaders.IfNoneMatch; if (etag != null) { ifNoneMatchState = GetEtagMatchState( etagHeader: ifNoneMatch, etag: etag, matchFoundState: PreconditionState.NotModified, matchNotFoundState: PreconditionState.ShouldProcess); } var now = DateTimeOffset.UtcNow; // 14.25 If-Modified-Since var ifModifiedSince = httpRequestHeaders.IfModifiedSince; if (lastModified.HasValue && ifModifiedSince.HasValue && ifModifiedSince <= now) { var modified = ifModifiedSince < lastModified; ifModifiedSinceState = modified ? PreconditionState.ShouldProcess : PreconditionState.NotModified; } // 14.28 If-Unmodified-Since var ifUnmodifiedSince = httpRequestHeaders.IfUnmodifiedSince; if (lastModified.HasValue && ifUnmodifiedSince.HasValue && ifUnmodifiedSince <= now) { var unmodified = ifUnmodifiedSince >= lastModified; ifUnmodifiedSinceState = unmodified ? PreconditionState.ShouldProcess : PreconditionState.PreconditionFailed; if (ifUnmodifiedSinceState == PreconditionState.PreconditionFailed) { Logger.IfUnmodifiedSincePreconditionFailed(lastModified, ifUnmodifiedSince); } } var state = GetMaxPreconditionState(ifMatchState, ifNoneMatchState, ifModifiedSinceState, ifUnmodifiedSinceState); return(state); }
private static void SetLastModifiedAndEtagHeaders(HttpResponse response, DateTimeOffset?lastModified, EntityTagHeaderValue etag) { var httpResponseHeaders = response.GetTypedHeaders(); if (lastModified.HasValue) { httpResponseHeaders.LastModified = lastModified; } if (etag != null) { httpResponseHeaders.ETag = etag; } }
private async Task <FileOperationResult <Image> > GetImageFileOperation(string type, string regionUrn, Guid id, int?width, int?height, Func <Task <FileOperationResult <Image> > > action, EntityTagHeaderValue etag = null) { try { var sw = Stopwatch.StartNew(); var result = (await CacheManager.GetAsync($"urn:{type}_by_id_operation:{id}", action, regionUrn)) .Adapt <FileOperationResult <Image> >(); if (!result.IsSuccess) { return(new FileOperationResult <Image>(result.IsNotFoundResult, result.Messages)); } if (result.ETag == etag) { return(new FileOperationResult <Image>(OperationMessages.NotModified)); } if ((width.HasValue || height.HasValue) && result?.Data?.Bytes != null) { result.Data.Bytes = ImageHelper.ResizeImage(result?.Data?.Bytes, width.Value, height.Value); result.ETag = EtagHelper.GenerateETag(HttpEncoder, result.Data.Bytes); result.LastModified = DateTime.UtcNow; if (width.Value != Configuration.ThumbnailImageSize.Width || height.Value != Configuration.ThumbnailImageSize.Height) { Logger.LogTrace($"{type}: Resized [{id}], Width [{width.Value}], Height [{height.Value}]"); } } sw.Stop(); return(new FileOperationResult <Image>(result.Messages) { Data = result.Data, ETag = result.ETag, LastModified = result.LastModified, ContentType = result.ContentType, Errors = result?.Errors, IsSuccess = result?.IsSuccess ?? false, OperationTime = sw.ElapsedMilliseconds }); } catch (Exception ex) { Logger.LogError(ex, $"GetImageFileOperation Error, Type [{type}], id [{id}]"); } return(new FileOperationResult <Image>("System Error")); }
/// <summary> /// Gets the EntityTagHeaderValue ETag>. /// </summary> /// <remarks>This signature uses types that are AspNetCore-specific.</remarks> internal override ETag GetETag(EntityTagHeaderValue etagHeaderValue) { return(InternalRequest.GetETag <TEntity>(etagHeaderValue)); }
public static ETag GetETag(this HttpRequestMessage request, EntityTagHeaderValue entityTagHeaderValue) { if (request == null) { throw Error.ArgumentNull("request"); } if (entityTagHeaderValue != null) { if (entityTagHeaderValue.Equals(EntityTagHeaderValue.Any)) { return(new ETag { IsAny = true }); } HttpConfiguration configuration = request.GetConfiguration(); if (configuration == null) { throw Error.InvalidOperation(SRResources.RequestMustContainConfiguration); } // get the etag handler, and parse the etag IDictionary <string, object> properties = configuration.GetETagHandler().ParseETag(entityTagHeaderValue) ?? new Dictionary <string, object>(); IList <object> parsedETagValues = properties.Select(property => property.Value).AsList(); // get property names from request ODataPath odataPath = request.ODataProperties().Path; IEdmModel model = request.GetModel(); IEdmNavigationSource source = odataPath.NavigationSource; if (model != null && source != null) { IList <IEdmStructuralProperty> concurrencyProperties = model.GetConcurrencyProperties(source).ToList(); IList <string> concurrencyPropertyNames = concurrencyProperties.OrderBy(c => c.Name).Select(c => c.Name).AsList(); ETag etag = new ETag(); if (parsedETagValues.Count != concurrencyPropertyNames.Count) { etag.IsWellFormed = false; } IEnumerable <KeyValuePair <string, object> > nameValues = concurrencyPropertyNames.Zip( parsedETagValues, (name, value) => new KeyValuePair <string, object>(name, value)); foreach (var nameValue in nameValues) { IEdmStructuralProperty property = concurrencyProperties.SingleOrDefault(e => e.Name == nameValue.Key); Contract.Assert(property != null); Type clrType = EdmLibHelpers.GetClrType(property.Type, model); Contract.Assert(clrType != null); if (nameValue.Value != null) { Type valueType = nameValue.Value.GetType(); etag[nameValue.Key] = valueType != clrType ? Convert.ChangeType(nameValue.Value, clrType, CultureInfo.InvariantCulture) : nameValue.Value; } else { etag[nameValue.Key] = nameValue.Value; } } return(etag); } } return(null); }
/// <summary> /// 基于服务器虚拟路径路径的ResumePhysicalFileResult /// </summary> /// <param name="fileName">文件全路径</param> /// <param name="contentType">Content-Type</param> /// <param name="etag">ETag</param> public ResumeVirtualFileResult(string fileName, MediaTypeHeaderValue contentType, EntityTagHeaderValue etag = null) : base(fileName, contentType) { EntityTag = etag; EnableRangeProcessing = true; }
protected override Task <HttpResponseMessage> CreateItemPutResponse(FileSystemInfo info, string localFilePath, bool itemExists) { // Check that we have a matching conditional If-Match request for existing resources if (itemExists) { // Get current etag EntityTagHeaderValue currentEtag = GetCurrentEtag(info); // Existing resources require an etag to be updated. if (Request.Headers.IfMatch == null) { HttpResponseMessage missingIfMatchResponse = Request.CreateErrorResponse( HttpStatusCode.PreconditionFailed, Resources.VfsController_MissingIfMatch); return(Task.FromResult(missingIfMatchResponse)); } bool isMatch = false; foreach (EntityTagHeaderValue etag in Request.Headers.IfMatch) { if (currentEtag.Equals(etag) || etag == EntityTagHeaderValue.Any) { isMatch = true; break; } } if (!isMatch) { HttpResponseMessage conflictFileResponse = Request.CreateErrorResponse( HttpStatusCode.PreconditionFailed, Resources.VfsController_EtagMismatch); conflictFileResponse.Headers.ETag = currentEtag; return(Task.FromResult(conflictFileResponse)); } } // Save file Stream fileStream = null; try { fileStream = GetFileWriteStream(localFilePath, fileExists: itemExists); return(Request.Content.CopyToAsync(fileStream) .Then(() => { // Successfully saved the file fileStream.Close(); fileStream = null; // Return either 204 No Content or 201 Created response HttpResponseMessage successFileResponse = Request.CreateResponse(itemExists ? HttpStatusCode.NoContent : HttpStatusCode.Created); // Set updated etag for the file successFileResponse.Headers.ETag = GetUpdatedEtag(localFilePath); return successFileResponse; }) .Catch((catchInfo) => { Tracer.TraceError(catchInfo.Exception); HttpResponseMessage conflictResponse = Request.CreateErrorResponse( HttpStatusCode.Conflict, RS.Format(Resources.VfsController_WriteConflict, localFilePath), catchInfo.Exception); if (fileStream != null) { fileStream.Close(); } return catchInfo.Handled(conflictResponse); })); } catch (Exception e) { Tracer.TraceError(e); HttpResponseMessage errorResponse = Request.CreateErrorResponse(HttpStatusCode.Conflict, RS.Format(Resources.VfsController_WriteConflict, localFilePath), e); if (fileStream != null) { fileStream.Close(); } return(Task.FromResult(errorResponse)); } }
protected override Task ExecuteAsync(HttpContext httpContext, string path, string contentType, DateTimeOffset?lastModified = null, EntityTagHeaderValue entityTag = null, bool enableRangeProcessing = false) { var webHostEnvironment = httpContext.RequestServices.GetRequiredService <IWebHostEnvironment>(); httpContext.RequestServices = new ServiceCollection() .AddSingleton(webHostEnvironment) .AddTransient <IActionResultExecutor <VirtualFileResult>, VirtualFileResultExecutor>() .AddTransient <ILoggerFactory, NullLoggerFactory>() .BuildServiceProvider(); var actionContext = new ActionContext(httpContext, new(), new()); var result = new VirtualFileResult(path, contentType) { LastModified = lastModified, EntityTag = entityTag, EnableRangeProcessing = enableRangeProcessing, }; return(result.ExecuteResultAsync(actionContext)); }
protected override Task <HttpResponseMessage> CreateItemGetResponse(FileSystemInfo info, string localFilePath) { // Get current etag EntityTagHeaderValue currentEtag = GetCurrentEtag(info); // Check whether we have a conditional If-None-Match request if (IsIfNoneMatchRequest(currentEtag)) { HttpResponseMessage notModifiedResponse = Request.CreateResponse(HttpStatusCode.NotModified); notModifiedResponse.Headers.ETag = currentEtag; return(Task.FromResult(notModifiedResponse)); } // Check whether we have a conditional range request containing both a Range and If-Range header field bool isRangeRequest = IsRangeRequest(currentEtag); // Generate file response Stream fileStream = null; try { fileStream = GetFileReadStream(localFilePath); MediaTypeHeaderValue mediaType = MediaTypeMap.GetMediaType(info.Extension); HttpResponseMessage successFileResponse = Request.CreateResponse(isRangeRequest ? HttpStatusCode.PartialContent : HttpStatusCode.OK); if (isRangeRequest) { successFileResponse.Content = new ByteRangeStreamContent(fileStream, Request.Headers.Range, mediaType, BufferSize); } else { successFileResponse.Content = new StreamContent(fileStream, BufferSize); successFileResponse.Content.Headers.ContentType = mediaType; } // Set etag for the file successFileResponse.Headers.ETag = currentEtag; return(Task.FromResult(successFileResponse)); } catch (InvalidByteRangeException invalidByteRangeException) { // The range request had no overlap with the current extend of the resource so generate a 416 (Requested Range Not Satisfiable) // including a Content-Range header with the current size. Tracer.TraceError(invalidByteRangeException); HttpResponseMessage invalidByteRangeResponse = Request.CreateErrorResponse(invalidByteRangeException); if (fileStream != null) { fileStream.Close(); } return(Task.FromResult(invalidByteRangeResponse)); } catch (Exception e) { // Could not read the file Tracer.TraceError(e); HttpResponseMessage errorResponse = Request.CreateErrorResponse(HttpStatusCode.NotFound, e); if (fileStream != null) { fileStream.Close(); } return(Task.FromResult(errorResponse)); } }
public async Task <FileOperationResult <Image> > ArtistSecondaryImage(Guid id, int imageId, int?width, int?height, EntityTagHeaderValue etag = null) { return(await GetImageFileOperation($"ArtistSecondaryThumbnail-{imageId}", data.Release.CacheRegionUrn(id), id, width, height, async() => { return await ArtistSecondaryImageAction(id, imageId, etag); }, etag)); }
private Task <FileOperationResult <Image> > ReleaseSecondaryImageAction(Guid id, int imageId, EntityTagHeaderValue etag = null) { try { var release = GetRelease(id); if (release == null) { return(Task.FromResult(new FileOperationResult <Image>(true, string.Format("Release Not Found [{0}]", id)))); } byte[] imageBytes = null; string artistFolder = null; string releaseFolder = null; try { // See if cover art file exists in release folder artistFolder = release.Artist.ArtistFileFolder(Configuration, Configuration.LibraryFolder); if (!Directory.Exists(artistFolder)) { Logger.LogWarning($"Artist Folder [{artistFolder}], Not Found For Artist `{release.Artist}`"); } else { releaseFolder = release.ReleaseFileFolder(artistFolder); if (!Directory.Exists(releaseFolder)) { Logger.LogWarning($"Release Folder [{releaseFolder}], Not Found For Release `{release}`"); } else { var releaseSecondaryImages = ImageHelper .FindImageTypeInDirectory(new DirectoryInfo(releaseFolder), ImageType.ReleaseSecondary) .ToArray(); if (releaseSecondaryImages.Length >= imageId && releaseSecondaryImages[imageId] != null) { imageBytes = File.ReadAllBytes(releaseSecondaryImages[imageId].FullName); } } } } catch (Exception ex) { Logger.LogError(ex, $"Error Reading Release Folder [{releaseFolder}] Artist Folder [{artistFolder}] For Artist `{release.Artist.Id}`"); } var image = new data.Image { Bytes = imageBytes, CreatedDate = release.CreatedDate, LastUpdated = release.LastUpdated }; return(Task.FromResult(GenerateFileOperationResult(id, image, etag))); } catch (Exception ex) { Logger.LogError($"Error fetching Release Thumbnail [{id}]", ex); } return(Task.FromResult(new FileOperationResult <Image>(OperationMessages.ErrorOccured))); }
/**************************************************************************/ private void ProcessResponseHttpHeaders(MacroscopeHttpTwoClientResponse Response) { HttpResponseMessage ResponseMessage = Response.GetResponse(); HttpResponseHeaders ResponseHeaders = ResponseMessage.Headers; HttpContentHeaders ContentHeaders = ResponseMessage.Content.Headers; /** Status Code ------------------------------------------------------ **/ this.SetStatusCode(ResponseMessage.StatusCode); this.SetErrorCondition(ResponseMessage.ReasonPhrase); try { switch (this.GetStatusCode()) { // 200 Range case HttpStatusCode.OK: this.SetIsNotRedirect(); break; // 300 Range case HttpStatusCode.Moved: this.SetErrorCondition(HttpStatusCode.Moved.ToString()); this.SetIsRedirect(); break; case HttpStatusCode.SeeOther: this.SetErrorCondition(HttpStatusCode.SeeOther.ToString()); this.SetIsRedirect(); break; case HttpStatusCode.Found: this.SetErrorCondition(HttpStatusCode.Redirect.ToString()); this.SetIsRedirect(); break; // 400 Range case HttpStatusCode.BadRequest: this.SetErrorCondition(HttpStatusCode.BadRequest.ToString()); this.SetIsNotRedirect(); break; case HttpStatusCode.Unauthorized: this.SetErrorCondition(HttpStatusCode.Unauthorized.ToString()); this.SetIsNotRedirect(); break; case HttpStatusCode.PaymentRequired: this.SetErrorCondition(HttpStatusCode.PaymentRequired.ToString()); this.SetIsNotRedirect(); break; case HttpStatusCode.Forbidden: this.SetErrorCondition(HttpStatusCode.Forbidden.ToString()); this.SetIsNotRedirect(); break; case HttpStatusCode.NotFound: this.SetErrorCondition(HttpStatusCode.NotFound.ToString()); this.SetIsNotRedirect(); break; case HttpStatusCode.MethodNotAllowed: this.SetErrorCondition(HttpStatusCode.MethodNotAllowed.ToString()); this.SetIsNotRedirect(); break; case HttpStatusCode.Gone: this.SetErrorCondition(HttpStatusCode.Gone.ToString()); this.SetIsNotRedirect(); break; case HttpStatusCode.RequestUriTooLong: this.SetErrorCondition(HttpStatusCode.RequestUriTooLong.ToString()); this.SetIsNotRedirect(); break; // Unhandled default: throw new MacroscopeDocumentException("Unhandled HttpStatusCode Type"); } } catch (MacroscopeDocumentException ex) { this.DebugMsg(string.Format("MacroscopeDocumentException: {0}", ex.Message)); } /** Raw HTTP Headers ------------------------------------------------- **/ this.SetHttpResponseStatusLine(Response: Response); this.SetHttpResponseHeaders(Response: Response); /** Server Information ----------------------------------------------- **/ /*{ * this.ServerName = ResponseHeaders.Server.First().ToString(); * }*/ /** PROBE HTTP HEADERS ----------------------------------------------- **/ /** Server HTTP Header ----------------------------------------------- **/ try { HttpHeaderValueCollection <ProductInfoHeaderValue> HeaderValue = ResponseHeaders.Server; if (HeaderValue != null) { if (HeaderValue.FirstOrDefault() != null) { this.SetServerName(HeaderValue.FirstOrDefault().ToString()); } } } catch (Exception ex) { this.DebugMsg(ex.Message); FindHttpResponseHeaderCallback Callback = delegate(IEnumerable <string> HeaderValues) { this.SetServerName(HeaderValues.First().ToString()); return(true); }; if (!this.FindHttpResponseHeader(ResponseHeaders: ResponseHeaders, HeaderName: "server", Callback: Callback)) { this.FindHttpContentHeader(ContentHeaders: ContentHeaders, HeaderName: "server", Callback: Callback); } } this.DebugMsg(string.Format("this.ServerName: {0}", this.ServerName)); /** Content-Type HTTP Header ----------------------------------------- **/ try { MediaTypeHeaderValue HeaderValue = ContentHeaders.ContentType; if (HeaderValue != null) { this.DebugMsg(string.Format("HeaderValue: {0}", HeaderValue)); this.MimeType = HeaderValue.MediaType; if (HeaderValue.CharSet != null) { this.SetCharacterSet(HeaderValue.CharSet); // TODO: Implement character set probing this.SetCharacterEncoding(NewEncoding: new UTF8Encoding()); } } } catch (Exception ex) { this.DebugMsg(string.Format("MediaType Exception: {0}", ex.Message)); this.MimeType = MacroscopeConstants.DefaultMimeType; } this.DebugMsg(string.Format("this.MimeType: {0}", this.MimeType)); /** Content-Length HTTP Header --------------------------------------- **/ try { long?HeaderValue = null; if (ContentHeaders.Contains("Content-Length")) { HeaderValue = ContentHeaders.ContentLength; } if (HeaderValue != null) { this.ContentLength = HeaderValue; } else { this.ContentLength = 0; } } catch (Exception ex) { this.DebugMsg(ex.Message); this.SetContentLength(Length: 0); FindHttpResponseHeaderCallback Callback = delegate(IEnumerable <string> HeaderValues) { this.SetContentLength(Length: long.Parse(HeaderValues.FirstOrDefault())); return(true); }; if (!this.FindHttpResponseHeader(ResponseHeaders: ResponseHeaders, HeaderName: "content-length", Callback: Callback)) { this.FindHttpContentHeader(ContentHeaders: ContentHeaders, HeaderName: "content-length", Callback: Callback); } } this.DebugMsg(string.Format("this.GetContentLength(): {0}", this.GetContentLength())); /** Content-Encoding HTTP Header ------------------------------------- **/ try { ICollection <string> HeaderValue = ContentHeaders.ContentEncoding; if (HeaderValue != null) { this.ContentEncoding = HeaderValue.FirstOrDefault(); } } catch (Exception ex) { this.DebugMsg(ex.Message); FindHttpResponseHeaderCallback Callback = delegate(IEnumerable <string> HeaderValues) { this.ContentEncoding = HeaderValues.FirstOrDefault(); return(true); }; if (!this.FindHttpResponseHeader(ResponseHeaders: ResponseHeaders, HeaderName: "content-encoding", Callback: Callback)) { this.FindHttpContentHeader(ContentHeaders: ContentHeaders, HeaderName: "content-encoding", Callback: Callback); } } if (string.IsNullOrEmpty(this.CompressionMethod) && (!string.IsNullOrEmpty(this.ContentEncoding))) { this.IsCompressed = true; this.CompressionMethod = this.ContentEncoding; } this.DebugMsg(string.Format("this.ContentEncoding: {0}", this.ContentEncoding)); this.DebugMsg(string.Format("this.CompressionMethod: {0}", this.CompressionMethod)); /** Date HTTP Header ------------------------------------------------- **/ try { DateTimeOffset?HeaderValue = ResponseHeaders.Date; if (HeaderValue != null) { this.DateServer = MacroscopeDateTools.ParseHttpDate(DateString: HeaderValue.ToString()); } } catch (Exception ex) { this.DebugMsg(ex.Message); this.DateServer = new DateTime(); FindHttpResponseHeaderCallback Callback = delegate(IEnumerable <string> HeaderValues) { this.DateServer = MacroscopeDateTools.ParseHttpDate(DateString: HeaderValues.First().ToString()); return(true); }; if (!this.FindHttpResponseHeader(ResponseHeaders: ResponseHeaders, HeaderName: "date", Callback: Callback)) { this.FindHttpContentHeader(ContentHeaders: ContentHeaders, HeaderName: "date", Callback: Callback); } } this.DebugMsg(string.Format("this.DateServer: {0}", this.DateServer)); /** Last-Modified HTTP Header ---------------------------------------- **/ try { DateTimeOffset?HeaderValue = ContentHeaders.LastModified; if (HeaderValue != null) { this.DateModified = MacroscopeDateTools.ParseHttpDate(DateString: HeaderValue.ToString()); } } catch (Exception ex) { this.DebugMsg(ex.Message); this.DateModified = new DateTime(); FindHttpResponseHeaderCallback Callback = delegate(IEnumerable <string> HeaderValues) { this.DateModified = MacroscopeDateTools.ParseHttpDate(DateString: HeaderValues.First().ToString()); return(true); }; if (!this.FindHttpResponseHeader(ResponseHeaders: ResponseHeaders, HeaderName: "last-modified", Callback: Callback)) { this.FindHttpContentHeader(ContentHeaders: ContentHeaders, HeaderName: "last-modified", Callback: Callback); } } this.DebugMsg(string.Format("this.DateModified: {0}", this.DateModified)); /** Expires HTTP Header ---------------------------------------------- **/ try { DateTimeOffset?HeaderValue = ContentHeaders.Expires; if (HeaderValue != null) { this.DateExpires = MacroscopeDateTools.ParseHttpDate(DateString: HeaderValue.ToString()); } } catch (Exception ex) { this.DebugMsg(ex.Message); this.DateExpires = new DateTime(); FindHttpResponseHeaderCallback Callback = delegate(IEnumerable <string> HeaderValues) { this.DateExpires = MacroscopeDateTools.ParseHttpDate(DateString: HeaderValues.First().ToString()); return(true); }; if (!this.FindHttpResponseHeader(ResponseHeaders: ResponseHeaders, HeaderName: "expires", Callback: Callback)) { this.FindHttpContentHeader(ContentHeaders: ContentHeaders, HeaderName: "expires", Callback: Callback); } } this.DebugMsg(string.Format("this.DateExpires: {0}", this.DateExpires)); /** HTST Policy HTTP Header ------------------------------------------ **/ // https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet // Strict-Transport-Security: max-age=31536000; includeSubDomains; preload { FindHttpResponseHeaderCallback Callback = delegate(IEnumerable <string> HeaderValues) { this.HypertextStrictTransportPolicy = true; return(true); }; if (!this.FindHttpResponseHeader(ResponseHeaders: ResponseHeaders, HeaderName: "strict-transport-security", Callback: Callback)) { this.FindHttpContentHeader(ContentHeaders: ContentHeaders, HeaderName: "strict-transport-security", Callback: Callback); } } this.DebugMsg(string.Format("this.HypertextStrictTransportPolicy: {0}", this.HypertextStrictTransportPolicy)); /** Location (Redirect) HTTP Header ---------------------------------- **/ try { Uri HeaderValue = ResponseHeaders.Location; if (HeaderValue != null) { this.SetUrlRedirectTo(Url: HeaderValue.ToString()); } } catch (Exception ex) { this.DebugMsg(ex.Message); FindHttpResponseHeaderCallback Callback = delegate(IEnumerable <string> HeaderValues) { this.SetUrlRedirectTo(Url: HeaderValues.FirstOrDefault().ToString()); return(true); }; if (!this.FindHttpResponseHeader(ResponseHeaders: ResponseHeaders, HeaderName: "location", Callback: Callback)) { this.FindHttpContentHeader(ContentHeaders: ContentHeaders, HeaderName: "location", Callback: Callback); } } this.DebugMsg(string.Format("this.GetIsRedirect(): {0}", this.GetIsRedirect())); this.DebugMsg(string.Format("this.GetUrlRedirectTo(): {0}", this.GetUrlRedirectTo())); /** Link HTTP Headers ------------------------------------------------ **/ { FindHttpResponseHeaderCallback Callback = delegate(IEnumerable <string> HeaderValues) { foreach (string HeaderValue in HeaderValues) { this.DebugMsg(string.Format("HeaderValue: {0}", HeaderValue)); this.ProcessHttpLinkHeader(HttpLinkHeader: HeaderValue); } return(true); }; if (!this.FindHttpResponseHeader(ResponseHeaders: ResponseHeaders, HeaderName: "link", Callback: Callback)) { this.FindHttpContentHeader(ContentHeaders: ContentHeaders, HeaderName: "link", Callback: Callback); } } /** ETag HTTP Header ------------------------------------------------- **/ try { EntityTagHeaderValue HeaderValue = ResponseHeaders.ETag; if (HeaderValue != null) { string ETagValue = HeaderValue.Tag; if (!string.IsNullOrEmpty(ETagValue)) { this.SetEtag(HeaderValue.Tag); } } } catch (Exception ex) { this.DebugMsg(ex.Message); FindHttpResponseHeaderCallback Callback = delegate(IEnumerable <string> HeaderValues) { string HeaderValue = HeaderValues.FirstOrDefault(); if (HeaderValue != null) { if (!string.IsNullOrEmpty(HeaderValue)) { this.SetEtag(HeaderValue); } } return(true); }; if (!this.FindHttpResponseHeader(ResponseHeaders: ResponseHeaders, HeaderName: "etag", Callback: Callback)) { this.FindHttpContentHeader(ContentHeaders: ContentHeaders, HeaderName: "etag", Callback: Callback); } } this.DebugMsg(string.Format("this.Etag: {0}", this.Etag)); /** WWW-AUTHENTICATE HTTP Header ------------------------------------- **/ // Reference: http://httpbin.org/basic-auth/user/passwd try { HttpHeaderValueCollection <AuthenticationHeaderValue> HeaderValue = ResponseHeaders.WwwAuthenticate; if (HeaderValue != null) { string Scheme = null; string Realm = null; foreach (AuthenticationHeaderValue AuthenticationValue in HeaderValue) { Scheme = AuthenticationValue.Scheme; string Parameter = AuthenticationValue.Parameter; Match Matched = Regex.Match(Parameter, "^[^\"]+\"([^\"]+)\""); if (Matched.Success) { Realm = Matched.Groups[1].Value; } } if (!string.IsNullOrEmpty(Scheme) && !string.IsNullOrEmpty(Realm)) { if (Scheme.ToLower() == "basic") { this.SetAuthenticationType(MacroscopeConstants.AuthenticationType.BASIC); this.SetAuthenticationRealm(Realm); } else { this.SetAuthenticationType(MacroscopeConstants.AuthenticationType.UNSUPPORTED); } } } } catch (Exception ex) { this.DebugMsg(ex.Message); } this.DebugMsg(string.Format("WwwAuthenticate: \"{0}\", Realm: \"{1}\"", this.GetAuthenticationType(), this.GetAuthenticationRealm())); /** Process Dates ---------------------------------------------------- **/ { if (this.DateServer.Date == new DateTime().Date) { this.DateServer = DateTime.UtcNow; } if (this.DateModified.Date == new DateTime().Date) { this.DateModified = this.DateServer; } } /** Process MIME Type ------------------------------------------------ **/ { Regex reIsHtml = new Regex(@"^(text/html|application/xhtml+xml)", RegexOptions.IgnoreCase); Regex reIsCss = new Regex(@"^text/css", RegexOptions.IgnoreCase); Regex reIsJavascript = new Regex(@"^(application/javascript|text/javascript)", RegexOptions.IgnoreCase); Regex reIsImage = new Regex(@"^image/(gif|png|jpeg|bmp|webp|vnd.microsoft.icon|x-icon)", RegexOptions.IgnoreCase); Regex reIsPdf = new Regex(@"^application/pdf", RegexOptions.IgnoreCase); Regex reIsAudio = new Regex(@"^audio/[a-z0-9]+", RegexOptions.IgnoreCase); Regex reIsVideo = new Regex(@"^video/[a-z0-9]+", RegexOptions.IgnoreCase); Regex reIsXml = new Regex(@"^(application|text)/(atom\+xml|xml)", RegexOptions.IgnoreCase); Regex reIsText = new Regex(@"^(text)/(plain)", RegexOptions.IgnoreCase); if (reIsHtml.IsMatch(this.MimeType)) { this.SetDocumentType(Type: MacroscopeConstants.DocumentType.HTML); } else if (reIsCss.IsMatch(this.MimeType)) { this.SetDocumentType(Type: MacroscopeConstants.DocumentType.CSS); } else if (reIsJavascript.IsMatch(this.MimeType)) { this.SetDocumentType(Type: MacroscopeConstants.DocumentType.JAVASCRIPT); } else if (reIsImage.IsMatch(this.MimeType)) { this.SetDocumentType(Type: MacroscopeConstants.DocumentType.IMAGE); } else if (reIsPdf.IsMatch(this.MimeType)) { this.SetDocumentType(Type: MacroscopeConstants.DocumentType.PDF); } else if (reIsAudio.IsMatch(this.MimeType)) { this.SetDocumentType(Type: MacroscopeConstants.DocumentType.AUDIO); } else if (reIsVideo.IsMatch(this.MimeType)) { this.SetDocumentType(Type: MacroscopeConstants.DocumentType.VIDEO); } else if (reIsXml.IsMatch(this.MimeType)) { this.SetDocumentType(Type: MacroscopeConstants.DocumentType.XML); } else if (reIsText.IsMatch(this.MimeType)) { this.SetDocumentType(Type: MacroscopeConstants.DocumentType.TEXT); } else { this.SetDocumentType(Type: MacroscopeConstants.DocumentType.BINARY); } } /** Process Cookies -------------------------------------------------- **/ // https://stackoverflow.com/questions/29224734/how-to-read-cookies-from-httpresponsemessage { try { CookieContainer CookieMonster = MacroscopeHttpTwoClient.GetCookieMonster(); CookieCollection Biscuits = CookieMonster.GetCookies(uri: this.GetUri()); this.AddCookies(Cookies: Biscuits); this.DebugMsg("cookies"); // CookieContainer CookieTin = MacroscopeHttpTwoClient.GetCookieMonster(); // string LimpBizkit = tin.GetCookieHeader( uri: Request.RequestUri ); } catch (Exception ex) { this.DebugMsg(ex.Message); } } return; }
/// <summary> /// Gets the <see cref="ETag{TEntity}"/> from the given request. /// </summary> /// <param name="request">The request.</param> /// <param name="entityTagHeaderValue">The entity tag header value.</param> /// <returns>The parsed <see cref="ETag{TEntity}"/>.</returns> public static ETag <TEntity> GetETag <TEntity>(this HttpRequestMessage request, EntityTagHeaderValue entityTagHeaderValue) { ETag etag = request.GetETag(entityTagHeaderValue); return(etag != null ? new ETag <TEntity> { ConcurrencyProperties = etag.ConcurrencyProperties, IsWellFormed = etag.IsWellFormed, IsAny = etag.IsAny, } : null); }
/// <summary> /// 基于服务器虚拟路径路径的ResumePhysicalFileResult /// </summary> /// <param name="fileName">文件全路径</param> /// <param name="contentType">Content-Type</param> /// <param name="etag">ETag</param> public ResumeVirtualFileResult(string fileName, string contentType, string etag = null) : this(fileName, MediaTypeHeaderValue.Parse(contentType), !string.IsNullOrEmpty(etag) ? EntityTagHeaderValue.Parse(etag) : null) { }
public virtual async Task RunIntegrationTest() { string dir = Guid.NewGuid().ToString("N"); string dirAddress = BaseAddress + _segmentDelimiter + dir; string dirAddressWithTerminatingSlash = dirAddress + _segmentDelimiter; // The %2520 is there to test that we can accept those characters. Here, %2520 is the URL encoded form, // and the actual file name has %20 (and not a space character!) string file = Guid.NewGuid().ToString("N") + "%2520" + ".txt"; string fileAddress = dirAddressWithTerminatingSlash + file; string fileAddressWithTerminatingSlash = fileAddress + _segmentDelimiter; string query = "?foo=bar"; string baseAddressWithQuery = BaseAddress + _segmentDelimiter + query; string dirAddressWithQuery = dirAddressWithTerminatingSlash + query; string fileAddressWithQuery = fileAddress + query; string deploymentFileAddress = null; string customDeploymentFileAddress = null; if (DeploymentClient != null) { deploymentFileAddress = string.Format("{0}{1}site{1}wwwroot{1}{2}{1}{3}", DeploymentBaseAddress, _segmentDelimiter, dir, file); customDeploymentFileAddress = string.Format("{0}{1}site{1}wwwroot{1}test{1}{2}{1}{3}", DeploymentBaseAddress, _segmentDelimiter, dir, file); } TestTracer.Trace("Starting RunIntegrationTest"); TestTracer.Trace("Dir - {0}", dirAddress); TestTracer.Trace("File - {0}", fileAddress); TestTracer.Trace("DeploymentFileAddress - {0}", deploymentFileAddress); HttpResponseMessage response; // Check not found file responses TestTracer.Trace("==== Check not found file responses"); response = await HttpGetAsync(dirAddress); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); response = await HttpGetAsync(dirAddressWithTerminatingSlash); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); response = await HttpGetAsync(fileAddress); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); response = await HttpGetAsync(fileAddressWithTerminatingSlash); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); // Check create file results in 201 response with etag TestTracer.Trace("==== Check create file results in 201 response with etag"); response = await HttpPutAsync(fileAddress, CreateUploadContent(_fileContent0)); await VerifyDeploymentAsync(deploymentFileAddress, HttpStatusCode.OK, _fileContent0); Assert.Equal(HttpStatusCode.Created, response.StatusCode); EntityTagHeaderValue originalEtag = response.Headers.ETag; Assert.NotNull(originalEtag); DateTimeOffset?lastModified = response.Content.Headers.LastModified; if (!_isScmEditorTest) { Assert.NotNull(lastModified); } // Check query string TestTracer.Trace("==== Check handle query string"); response = await HttpGetAsync(baseAddressWithQuery); Assert.Equal(HttpStatusCode.OK, response.StatusCode); response = await HttpGetAsync(dirAddressWithQuery); Assert.Equal(HttpStatusCode.OK, response.StatusCode); response = await HttpGetAsync(fileAddressWithQuery); Assert.Equal(HttpStatusCode.OK, response.StatusCode); // Check that we get a 200 (OK) on created file with the correct etag TestTracer.Trace("==== Check that we get a 200 (OK) on created file with the correct etag"); response = await HttpGetAsync(fileAddress); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(originalEtag, response.Headers.ETag); Assert.Equal(lastModified, response.Content.Headers.LastModified); Assert.Equal(_fileMediaType, response.Content.Headers.ContentType); // Check that we get a 200 (OK) on created file using HEAD with the correct etag TestTracer.Trace("==== Check that we get a 200 (OK) on created file using HEAD with the correct etag"); using (HttpRequestMessage headReq = new HttpRequestMessage()) { headReq.Method = HttpMethod.Head; headReq.RequestUri = new Uri(fileAddress); response = await Client.SendAsync(headReq); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(originalEtag, response.Headers.ETag); Assert.Equal(_fileMediaType, response.Content.Headers.ContentType); } // Check that we get a 304 (Not Modified) response if matching If-None-Match TestTracer.Trace("==== Check that we get a 304 (Not Modified) response if matching If-None-Match"); using (HttpRequestMessage ifNoneMatchReq = new HttpRequestMessage()) { ifNoneMatchReq.RequestUri = new Uri(fileAddress); ifNoneMatchReq.Headers.IfNoneMatch.Add(originalEtag); response = await HttpSendAsync(ifNoneMatchReq); Assert.Equal(HttpStatusCode.NotModified, response.StatusCode); Assert.Equal(originalEtag, response.Headers.ETag); } // Check that we get a 200 (OK) response if not matching If-None-Match TestTracer.Trace("==== Check that we get a 200 (OK) response if not matching If-None-Match"); using (HttpRequestMessage ifNoneMatchReqBadEtag = new HttpRequestMessage()) { ifNoneMatchReqBadEtag.RequestUri = new Uri(fileAddress); ifNoneMatchReqBadEtag.Headers.IfNoneMatch.Add(new EntityTagHeaderValue("\"NotMatching\"")); response = await HttpSendAsync(ifNoneMatchReqBadEtag); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(originalEtag, response.Headers.ETag); } // Check that If-Range request with range returns 206 (Partial Content) TestTracer.Trace("==== Check that If-Range request with range returns 206 (Partial Content)"); using (HttpRequestMessage ifRangeReq = new HttpRequestMessage()) { ifRangeReq.RequestUri = new Uri(fileAddress); ifRangeReq.Headers.IfRange = new RangeConditionHeaderValue(originalEtag); ifRangeReq.Headers.Range = new RangeHeaderValue(0, 0) { Unit = "bytes" }; response = await HttpSendAsync(ifRangeReq); Assert.Equal(HttpStatusCode.PartialContent, response.StatusCode); Assert.Equal(originalEtag, response.Headers.ETag); Assert.Equal(1, response.Content.Headers.ContentLength); Assert.Equal(new ContentRangeHeaderValue(0, 0, _fileContent0.Length), response.Content.Headers.ContentRange); } // Check that If-Range request with no range returns 200 (OK) TestTracer.Trace("==== Check that If-Range request with no range returns 200 (OK)"); using (HttpRequestMessage ifRangeReqNoRange = new HttpRequestMessage()) { ifRangeReqNoRange.RequestUri = new Uri(fileAddress); ifRangeReqNoRange.Headers.IfRange = new RangeConditionHeaderValue(originalEtag); response = await HttpSendAsync(ifRangeReqNoRange); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(originalEtag, response.Headers.ETag); } // Check that If-Range request with bad range returns 416 (Requested Range Not Satisfiable) // including a Content-Range header TestTracer.Trace("==== Check that If-Range request with bad range returns 416 (Requested Range Not Satisfiable)"); using (HttpRequestMessage ifRangeReqBadRange = new HttpRequestMessage()) { ifRangeReqBadRange.RequestUri = new Uri(fileAddress); ifRangeReqBadRange.Headers.IfRange = new RangeConditionHeaderValue(originalEtag); ifRangeReqBadRange.Headers.Range = new RangeHeaderValue(100, 100) { Unit = "bytes" }; response = await HttpSendAsync(ifRangeReqBadRange); Assert.Equal(HttpStatusCode.RequestedRangeNotSatisfiable, response.StatusCode); Assert.Equal(_fileContentRange, response.Content.Headers.ContentRange); } // Check that we get a root directory view TestTracer.Trace("==== Check that we get a root directory view"); response = await HttpGetAsync(BaseAddress); Assert.Equal(_dirMediaType, response.Content.Headers.ContentType); // Check that we get a directory view from folder TestTracer.Trace("==== Check that we get a directory view from folder"); response = await HttpGetAsync(dirAddress); Assert.Equal(_dirMediaType, response.Content.Headers.ContentType); // Check various redirects between files and folders HttpClientHandler redirectHandler = HttpClientHelper.CreateClientHandler(BaseAddress, KuduClient.Credentials); redirectHandler.AllowAutoRedirect = false; using (HttpClient redirectClient = HttpClientHelper.CreateClient(BaseAddress, KuduClient.Credentials, redirectHandler)) { // Ensure that requests to root without slash is redirected to one with slash TestTracer.Trace("==== Ensure that requests to root without slash is redirected to one with slash"); response = await HttpGetAsync(BaseAddress, redirectClient); Assert.Equal(HttpStatusCode.TemporaryRedirect, response.StatusCode); Assert.Equal(BaseAddress + _segmentDelimiter, response.Headers.Location.AbsoluteUri); // Ensure that requests to directory without slash is redirected to one with slash TestTracer.Trace("==== Ensure that requests to directory without slash is redirected to one with slash"); response = await HttpGetAsync(dirAddress, redirectClient); Assert.Equal(HttpStatusCode.TemporaryRedirect, response.StatusCode); Assert.Equal(dirAddressWithTerminatingSlash, response.Headers.Location.AbsoluteUri); // Ensure that requests to file with slash is redirected to one without slash TestTracer.Trace("==== Ensure that requests to file with slash is redirected to one without slash"); response = await HttpGetAsync(fileAddressWithTerminatingSlash, redirectClient); Assert.Equal(HttpStatusCode.TemporaryRedirect, response.StatusCode); Assert.Equal(fileAddress, response.Headers.Location.AbsoluteUri); } // Check that 2nd create attempt fails TestTracer.Trace("==== Check that 2nd create attempt fails"); response = await HttpPutAsync(fileAddress, CreateUploadContent(_fileContent0)); Assert.Equal(HttpStatusCode.PreconditionFailed, response.StatusCode); Assert.Equal(originalEtag, response.Headers.ETag); // Check that we can't update a directory TestTracer.Trace("==== Check that we can't update a directory"); response = await HttpPutAsync(dirAddress, CreateUploadContent(_fileContent0)); Assert.Equal(HttpStatusCode.Conflict, response.StatusCode); // Check that we can't delete a non-empty directory TestTracer.Trace("==== Check that we can't delete a non-empty directory"); response = await HttpDeleteAsync(dirAddress); Assert.Equal(HttpStatusCode.Conflict, response.StatusCode); EntityTagHeaderValue updatedEtag; if (_testConflictingUpdates) { // Update file with first edit based on original etag TestTracer.Trace("==== Update file with first edit based on original etag"); using (HttpRequestMessage update1 = new HttpRequestMessage()) { update1.Method = HttpMethod.Put; update1.RequestUri = new Uri(fileAddress); update1.Headers.IfMatch.Add(originalEtag); update1.Content = CreateUploadContent(_fileContent1); response = await HttpSendAsync(update1); await VerifyDeploymentAsync(deploymentFileAddress, HttpStatusCode.OK, _fileContent1); Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); Assert.NotNull(response.Headers.ETag); Assert.NotEqual(originalEtag, response.Headers.ETag); updatedEtag = response.Headers.ETag; } // Update file with second edit based on original etag (non-conflicting merge) TestTracer.Trace("==== Update file with second edit based on original etag (non-conflicting merge)"); using (HttpRequestMessage update2 = new HttpRequestMessage()) { update2.Method = HttpMethod.Put; update2.RequestUri = new Uri(fileAddress); update2.Headers.IfMatch.Add(originalEtag); update2.Content = CreateUploadContent(_fileContent2); response = await HttpSendAsync(update2); await VerifyDeploymentAsync(deploymentFileAddress, HttpStatusCode.OK, _fileContent3); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.NotNull(response.Headers.ETag); Assert.NotEqual(updatedEtag, response.Headers.ETag); Assert.Equal(_fileMediaType, response.Content.Headers.ContentType); updatedEtag = response.Headers.ETag; } // Update file with third edit based on original etag (non-conflicting merge) TestTracer.Trace("==== Update file with third edit based on original etag (non-conflicting merge)"); using (HttpRequestMessage update3 = new HttpRequestMessage()) { update3.Method = HttpMethod.Put; update3.RequestUri = new Uri(fileAddress); update3.Headers.IfMatch.Add(originalEtag); update3.Content = CreateUploadContent(_fileContent3); response = await HttpSendAsync(update3); await VerifyDeploymentAsync(deploymentFileAddress, HttpStatusCode.OK, _fileContent3); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.NotNull(response.Headers.ETag); Assert.Equal(updatedEtag, response.Headers.ETag); Assert.Equal(_fileMediaType, response.Content.Headers.ContentType); updatedEtag = response.Headers.ETag; } // Update file with forth edit based on original etag (conflicting) TestTracer.Trace("==== Update file with forth edit based on original etag (conflicting)"); using (HttpRequestMessage update4 = new HttpRequestMessage()) { update4.Method = HttpMethod.Put; update4.RequestUri = new Uri(fileAddress); update4.Headers.IfMatch.Add(originalEtag); update4.Content = CreateUploadContent(_fileContent4); response = await HttpSendAsync(update4); await VerifyDeploymentAsync(deploymentFileAddress, HttpStatusCode.OK, _fileContent3); Assert.Equal(HttpStatusCode.Conflict, response.StatusCode); Assert.Equal(_conflictMediaType, response.Content.Headers.ContentType); Assert.Null(response.Headers.ETag); string content = await response.Content.ReadAsStringAsync(); Assert.True(Regex.IsMatch(content, _conflict)); } // Update file with fifth edit based on invalid etag TestTracer.Trace("==== Update file with fifth edit based on invalid etag"); using (HttpRequestMessage update5 = new HttpRequestMessage()) { update5.Method = HttpMethod.Put; update5.RequestUri = new Uri(fileAddress); update5.Headers.IfMatch.Add(new EntityTagHeaderValue("\"invalidetag\"")); update5.Content = CreateUploadContent(_fileContent1); response = await HttpSendAsync(update5); await VerifyDeploymentAsync(deploymentFileAddress, HttpStatusCode.OK, _fileContent3); Assert.Equal(HttpStatusCode.PreconditionFailed, response.StatusCode); Assert.Equal(updatedEtag, response.Headers.ETag); } // Check that update with wildcard etag succeeds TestTracer.Trace("==== Check that update with wildcard etag succeeds"); using (HttpRequestMessage update6 = new HttpRequestMessage()) { update6.Method = HttpMethod.Put; update6.RequestUri = new Uri(fileAddress); update6.Headers.IfMatch.Add(EntityTagHeaderValue.Any); update6.Content = CreateUploadContent(_fileContent1); response = await HttpSendAsync(update6); await VerifyDeploymentAsync(deploymentFileAddress, HttpStatusCode.OK, _fileContent1); Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); Assert.NotNull(response.Headers.ETag); Assert.NotEqual(originalEtag, response.Headers.ETag); updatedEtag = response.Headers.ETag; } TestTracer.Trace("==== Check that concurrent updates work"); List <Task <HttpResponseMessage> > concurrentUpdates = new List <Task <HttpResponseMessage> >(); for (int cnt = 0; cnt < 16; cnt++) { HttpRequestMessage concurrentRequest = new HttpRequestMessage() { Method = HttpMethod.Put, RequestUri = new Uri(fileAddress + "?nodeploy"), Content = CreateUploadContent(_fileContent2) }; concurrentRequest.Headers.IfMatch.Add(EntityTagHeaderValue.Any); concurrentUpdates.Add(HttpSendAsync(concurrentRequest)); } await Task.WhenAll(concurrentUpdates); IEnumerable <HttpResponseMessage> concurrentResponses = concurrentUpdates.Select(update => update.Result); foreach (HttpResponseMessage concurrentResponse in concurrentResponses) { // NoContent is the expected success case. // PreConditionFailed can happen due to race condition between LibGit2Sharp cleanup and lock acquisition and release. // In PreConditionFailed case, nothing is written and the repo isn't updated or corrupted. // Conflict can happen due to multiple threads pass LibGit2Sharp cleanup step (one after another) and race to write to the same file. // This is an edge case for a legacy feature Assert.True( concurrentResponse.StatusCode == HttpStatusCode.NoContent || concurrentResponse.StatusCode == HttpStatusCode.PreconditionFailed || concurrentResponse.StatusCode == HttpStatusCode.Conflict, $"Status code expected to be either {HttpStatusCode.NoContent}, {HttpStatusCode.PreconditionFailed} or {HttpStatusCode.Conflict} but got {concurrentResponse.StatusCode}"); } TestTracer.Trace("==== Check that 'nodeploy' doesn't deploy and that the old content remains."); using (HttpRequestMessage request = new HttpRequestMessage()) { request.Method = HttpMethod.Put; request.RequestUri = new Uri(fileAddress + "?nodeploy"); request.Headers.IfMatch.Add(EntityTagHeaderValue.Any); request.Content = CreateUploadContent(_fileContent2); response = await HttpSendAsync(request); await VerifyDeploymentAsync(deploymentFileAddress, HttpStatusCode.OK, _fileContent1); Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); Assert.NotNull(response.Headers.ETag); Assert.NotEqual(originalEtag, response.Headers.ETag); updatedEtag = response.Headers.ETag; } TestTracer.Trace("==== Check passing custom commit message."); using (HttpRequestMessage request = new HttpRequestMessage()) { request.Method = HttpMethod.Put; request.RequestUri = new Uri(fileAddress + "?message=helloworld"); request.Headers.IfMatch.Add(EntityTagHeaderValue.Any); request.Content = CreateUploadContent(_fileContent3); response = await HttpSendAsync(request); await VerifyDeploymentAsync(deploymentFileAddress, HttpStatusCode.OK, _fileContent3); Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); Assert.NotNull(response.Headers.ETag); Assert.NotEqual(originalEtag, response.Headers.ETag); updatedEtag = response.Headers.ETag; } TestTracer.Trace("==== Check that custom deployment script works"); using (HttpRequestMessage update7 = new HttpRequestMessage()) { // Upload custom deployment scripts TestTracer.Trace("==== Upload custom deployment scripts"); updatedEtag = await UploadCustomDeploymentScripts(); // Upload content and validate that it gets deployed TestTracer.Trace("==== Upload content and validate that it gets deployed"); update7.Method = HttpMethod.Put; update7.RequestUri = new Uri(fileAddress); update7.Headers.IfMatch.Add(updatedEtag); update7.Content = CreateUploadContent(_fileContent2); response = await HttpSendAsync(update7); await VerifyDeploymentAsync(customDeploymentFileAddress, HttpStatusCode.OK, _fileContent2); Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); Assert.NotNull(response.Headers.ETag); Assert.NotEqual(originalEtag, response.Headers.ETag); updatedEtag = response.Headers.ETag; // Remove custom deployment scripts TestTracer.Trace("==== Remove custom deployment scripts"); updatedEtag = await RemoveCustomDeploymentScripts(); } // Check that delete with invalid etag fails TestTracer.Trace("==== Check that delete with invalid etag fails"); using (HttpRequestMessage deleteRequest = new HttpRequestMessage()) { deleteRequest.Method = HttpMethod.Delete; deleteRequest.RequestUri = new Uri(fileAddress); deleteRequest.Headers.IfMatch.Add(new EntityTagHeaderValue("\"invalidetag\"")); response = await HttpSendAsync(deleteRequest); await VerifyDeploymentAsync(deploymentFileAddress, HttpStatusCode.OK, _fileContent2); Assert.Equal(HttpStatusCode.PreconditionFailed, response.StatusCode); Assert.Equal(updatedEtag, response.Headers.ETag); } // Check that delete with conflict fails TestTracer.Trace("==== Check that delete with conflict fails"); using (HttpRequestMessage deleteRequest = new HttpRequestMessage()) { deleteRequest.Method = HttpMethod.Delete; deleteRequest.RequestUri = new Uri(fileAddress); deleteRequest.Headers.IfMatch.Add(originalEtag); response = await HttpSendAsync(deleteRequest); await VerifyDeploymentAsync(deploymentFileAddress, HttpStatusCode.OK, _fileContent2); Assert.Equal(HttpStatusCode.Conflict, response.StatusCode); } // Check that delete with valid etag succeeds TestTracer.Trace("==== Check that delete with valid etag succeeds"); using (HttpRequestMessage deleteRequest = new HttpRequestMessage()) { deleteRequest.Method = HttpMethod.Delete; deleteRequest.RequestUri = new Uri(fileAddress); deleteRequest.Headers.IfMatch.Add(updatedEtag); response = await HttpSendAsync(deleteRequest); await VerifyDeploymentAsync(deploymentFileAddress, HttpStatusCode.NotFound, null); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } // Check that 2nd delete attempt fails TestTracer.Trace("==== Check that 2nd delete attempt fails"); using (HttpRequestMessage deleteRequest = new HttpRequestMessage()) { deleteRequest.Method = HttpMethod.Delete; deleteRequest.RequestUri = new Uri(fileAddress); deleteRequest.Headers.IfMatch.Add(updatedEtag); response = await HttpSendAsync(deleteRequest); await VerifyDeploymentAsync(deploymentFileAddress, HttpStatusCode.NotFound, null); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } } else { // Check that update with correct etag generates 204 Response with new etag TestTracer.Trace("==== Check that update with correct etag generates 204 Response with new etag"); using (HttpRequestMessage updateRequest = new HttpRequestMessage()) { updateRequest.Method = HttpMethod.Put; updateRequest.RequestUri = new Uri(fileAddress); updateRequest.Headers.IfMatch.Add(originalEtag); updateRequest.Content = CreateUploadContent(_fileContent1); response = await HttpSendAsync(updateRequest); Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); Assert.NotNull(response.Headers.ETag); Assert.NotEqual(originalEtag, response.Headers.ETag); updatedEtag = response.Headers.ETag; } // Check that 2nd create attempt fails TestTracer.Trace("==== Check that 2nd create attempt fails"); using (HttpRequestMessage updateRequest = new HttpRequestMessage()) { updateRequest.Method = HttpMethod.Put; updateRequest.RequestUri = new Uri(fileAddress); updateRequest.Headers.IfMatch.Add(originalEtag); updateRequest.Content = CreateUploadContent(_fileContent2); response = await HttpSendAsync(updateRequest); Assert.Equal(HttpStatusCode.PreconditionFailed, response.StatusCode); Assert.Equal(updatedEtag, response.Headers.ETag); } // Check that update with invalid etag fails TestTracer.Trace("==== Check that update with invalid etag fails"); using (HttpRequestMessage updateRequest = new HttpRequestMessage()) { updateRequest.Method = HttpMethod.Put; updateRequest.RequestUri = new Uri(fileAddress); updateRequest.Headers.IfMatch.Add(new EntityTagHeaderValue("\"invalidetag\"")); updateRequest.Content = CreateUploadContent(_fileContent1); response = await HttpSendAsync(updateRequest); Assert.Equal(HttpStatusCode.PreconditionFailed, response.StatusCode); Assert.Equal(updatedEtag, response.Headers.ETag); } // Check that update with wildcard etag succeeds TestTracer.Trace("==== Check that update with wildcard etag succeeds"); using (HttpRequestMessage updateRequest = new HttpRequestMessage()) { updateRequest.Method = HttpMethod.Put; updateRequest.RequestUri = new Uri(fileAddress); updateRequest.Headers.IfMatch.Add(EntityTagHeaderValue.Any); updateRequest.Content = CreateUploadContent(_fileContent1); response = await HttpSendAsync(updateRequest); Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); Assert.NotNull(response.Headers.ETag); Assert.NotEqual(originalEtag, response.Headers.ETag); updatedEtag = response.Headers.ETag; } // Check that delete with invalid etag fails TestTracer.Trace("==== Check that delete with invalid etag fails"); using (HttpRequestMessage deleteRequest = new HttpRequestMessage()) { deleteRequest.Method = HttpMethod.Delete; deleteRequest.RequestUri = new Uri(fileAddress); deleteRequest.Headers.IfMatch.Add(new EntityTagHeaderValue("\"invalidetag\"")); response = await HttpSendAsync(deleteRequest); Assert.Equal(HttpStatusCode.PreconditionFailed, response.StatusCode); Assert.Equal(updatedEtag, response.Headers.ETag); } // Check that delete with valid etag succeeds TestTracer.Trace("==== Check that delete with valid etag succeeds"); using (HttpRequestMessage deleteRequest = new HttpRequestMessage()) { deleteRequest.Method = HttpMethod.Delete; deleteRequest.RequestUri = new Uri(fileAddress); deleteRequest.Headers.IfMatch.Add(updatedEtag); response = await HttpSendAsync(deleteRequest); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } // Check that 2nd delete attempt fails TestTracer.Trace("==== Check that 2nd delete attempt fails"); using (HttpRequestMessage deleteRequest = new HttpRequestMessage()) { deleteRequest.Method = HttpMethod.Delete; deleteRequest.RequestUri = new Uri(fileAddress); deleteRequest.Headers.IfMatch.Add(updatedEtag); response = await HttpSendAsync(deleteRequest); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } // Check that we can delete an empty directory TestTracer.Trace("==== Check that we can delete an empty directory"); response = await HttpDeleteAsync(dirAddress); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } }
internal virtual ETag GetETag(EntityTagHeaderValue etagHeaderValue) { return(Request.GetETag(etagHeaderValue)); }
/// <summary> /// Gets the EntityTagHeaderValue ETag>. /// </summary> /// <remarks>This function uses types that are AspNet-specific.</remarks> public ETag GetETag <TEntity>(EntityTagHeaderValue etagHeaderValue) { return(this.innerRequest.GetETag <TEntity>(etagHeaderValue)); }
private static void RunQueries() { HttpClient client = new HttpClient(); HttpRequestMessage request; HttpResponseMessage response; // Retrieving an entity for the first time. Observe that the returned payload contains the annotation // @odata.etag indicating the ETag associated with that customer. Console.WriteLine("Retrieving a single customer at {0}/odata/Customers(1)", serverUrl); Console.WriteLine(); request = new HttpRequestMessage(HttpMethod.Get, serverUrl + "/odata/Customers(1)"); response = client.SendAsync(request).Result; response.EnsureSuccessStatusCode(); dynamic customer = JObject.Parse(response.Content.ReadAsStringAsync().Result); Console.WriteLine(customer); Console.WriteLine(); Console.WriteLine("-----------------------------------------------------"); string etag = customer["@odata.etag"]; // Retrieving the same customer as in the previous request but only if the ETag doesn't match the one // specified in the If-None-Match header. We are sending the ETag value that we obtained from the previous // request, so we expect to see a 304 (Not Modified) response. Console.WriteLine("Retrieving the customer at {0}/odata/Customers(1) when the Etag value sent matches"); request = new HttpRequestMessage(HttpMethod.Get, serverUrl + "/odata/Customers(1)"); request.Headers.IfNoneMatch.Add(EntityTagHeaderValue.Parse(etag)); response = client.SendAsync(request).Result; Console.WriteLine("The response status code is: {0}", response.StatusCode); Console.WriteLine(); Console.WriteLine("-----------------------------------------------------"); // Retrieving the same customer as in the previous request but only if the ETag doesn't match the one // specified in the If-None-Match header. We are sending a different ETag value, so we expect to see a 200 // (OK) response. Console.WriteLine("Retrieving the customer at {0}/odata/Customers(1) when the Etag value sent matches"); request = new HttpRequestMessage(HttpMethod.Get, serverUrl + "/odata/Customers(1)"); request.Headers.IfNoneMatch.Add(EntityTagHeaderValue.Parse("W/\"MQ==\"")); response = client.SendAsync(request).Result; Console.WriteLine("The response status code is {0}", response.StatusCode); Console.WriteLine(JObject.Parse(response.Content.ReadAsStringAsync().Result)); Console.WriteLine(); Console.WriteLine("-----------------------------------------------------"); // Removing the annotations from the customer object as they are not required on the following requests and // changing the age value. customer.Age = 99; customer.Remove("@odata.etag"); customer.Remove("@odata.context"); // Trying to update the customer using a different ETag value than the ETag on the previous request. The // server will return a 412 (Precondition Failed) response. request = new HttpRequestMessage(HttpMethod.Put, serverUrl + "/odata/Customers(1)"); // Setting up a different ETag value. request.Headers.IfMatch.Add(EntityTagHeaderValue.Parse("W/\"MQ==\"")); request.Content = new ObjectContent <JObject>(customer, new JsonMediaTypeFormatter()); Console.WriteLine("Trying to update the Customer using a different ETag value on the If-Match header and failing"); response = client.SendAsync(request).Result; Console.WriteLine("The response status code is {0}", response.StatusCode); Console.WriteLine(); Console.WriteLine("-----------------------------------------------------"); // Trying to update the customer using the ETag value retrieved from the first request. The server will // process the request and return a 200 (OK) response. request = new HttpRequestMessage(HttpMethod.Put, serverUrl + "/odata/Customers(1)"); request.Headers.IfMatch.Add(EntityTagHeaderValue.Parse(etag)); request.Content = new ObjectContent <JObject>(customer, new JsonMediaTypeFormatter()); Console.WriteLine("Trying to update a Customer using the same ETag value on the If-Match header and succeeding"); Console.WriteLine(); response = client.SendAsync(request).Result; Console.Write(JObject.Parse(response.Content.ReadAsStringAsync().Result)); Console.WriteLine(); Console.WriteLine("-----------------------------------------------------"); // Trying to update the customer using the If-Match header and sending an entity that doesn't exist on the // database. The behavior in this case is "strict update", meaning that the server shouldn't try to insert // the entity instead, so the answer we receive is 404 (Not Found). request = new HttpRequestMessage(HttpMethod.Put, serverUrl + "/odata/Customers(30)"); request.Headers.IfMatch.Add(EntityTagHeaderValue.Parse("W/\"MQ==\"")); Customer newCustomer = new Customer { Id = 30, Name = "New customer", Age = 30, Version = 0 }; request.Content = new ObjectContent <Customer>(newCustomer, new JsonMediaTypeFormatter()); Console.WriteLine("Trying to update a non existing customer with the If-Match header present"); Console.WriteLine(); response = client.SendAsync(request).Result; Console.WriteLine("The response status code is {0}", response.StatusCode); Console.WriteLine(); Console.WriteLine("-----------------------------------------------------"); // Trying the same request but without the If-Match header. The behavior in this case is "update or insert", // meaning that the server should try to insert the entity if it doesn't exist, so the answer we receive is // 201 (Created). request = new HttpRequestMessage(HttpMethod.Put, serverUrl + "/odata/Customers(30)"); request.Content = new ObjectContent <Customer>(newCustomer, new JsonMediaTypeFormatter()); Console.WriteLine("Trying to update a non existing customer without the If-Match header"); Console.WriteLine(); response = client.SendAsync(request).Result; Console.WriteLine("The response status code is {0}", response.StatusCode); Console.WriteLine(JObject.Parse(response.Content.ReadAsStringAsync().Result)); Console.WriteLine(); Console.WriteLine("-----------------------------------------------------"); }
public NotModifiedResponse(EntityTagHeaderValue etag) : base(HttpStatusCode.NotModified) { this.Headers.ETag = etag; }
public RangeConditionHeaderValue (EntityTagHeaderValue entityTag) { EntityTag = entityTag; }
private static bool EtagEquals(HttpRequestMessage request, EntityTagHeaderValue currentEtag) { return(request.Headers.IfNoneMatch != null && request.Headers.IfNoneMatch.Any(etag => currentEtag.Equals(etag))); }