#wcf #http-headers #etag #if-modified-since
#wcf #http-заголовки #этаг #if-modified-since
Вопрос:
Я пытаюсь заставить заголовок «if-modified-since» работать с моей веб-службой WCF.
Когда пользователь делает запрос к моей службе, я добавляю ETag к исходящему ответу, который содержит временную метку запроса следующим образом:
var tag = String.Format(""{0:o}"", new DateTimeOffset(DateTime.Now));
Это приводит к следующему заголовку ETag:
ETag: "2011-10-27T13:09:39.6242263-04:00"
Затем я беру это значение и повторяю его как заголовок if-modified-since для последующих запросов, подобных этому:
If-Modified-Since:2011-10-27T13:09:39.6242263-04:00
Когда я проверяю WebOperationContext.Current.Заголовки.IfModifiedSince, я никогда не получаю предоставленное значение. Значение зафиксировано на уровне «31.12.1969, 7:00:00 вечера».
Что я делаю не так?
Обновить
Я должен добавить, что с помощью Fiddler я могу установить любое значение для заголовка If-Modified-Since и при этом получить то же значение 1969 в коде.
Комментарии:
1. Можете ли вы показать, как вы добавляете ETag к исходящему ответу? В MSDN есть примечание о том, что предпочтительнее использовать метод SetETag() OutgoingResponse для обеспечения его правильных кавычек, а не добавлять его непосредственно в свойство headers или ETag .
2. Не уверен, какое это имеет значение, поскольку ETag отправляется в ответе правильно. Моя проблема заключается в получении запроса, который имеет значение для заголовка if-modified-since. Я показываю код ETag только для того, чтобы показать, как было получено значение даты / времени, используемое для заголовка if-modified-since.
3. Это совпадение, что значение, которое вы видите в получающем веб-методе, является эпохой -04:00, а ваш ETag заканчивается на «-04:00»? 🙂 Вы
DateTime.Pars
где-то пишете?4. Мы используем DateTimeOffset для генерации ETag, потому что наши клиенты могут находиться в разных часовых поясах от сервера, и мы хотим убедиться, что дата отражает время сервера при отправке обратно в заголовке if-modified-since.
5. @SonOfPirate, это был не мой вопрос. 🙂
Ответ №1:
Во-первых, If-Modified-Since
речь идет об условном получении информации о времени последней модификации ресурса, в то время ETag
как речь идет об условном получении информации об идентификаторе ресурсов, поэтому, пожалуйста, будьте осторожны при смешивании двух понятий.
Правильный способ реализации поддержки If-Modified-Since в службе WCF — использовать CheckConditionalRetrieve
передачу DateTime
значения в WebOperationContext.Current.IncomingRequest
объекте — см. Код ниже для этого. Если значение заголовка IMS находится перед датой, на которую вы переходите CheckConditionalRetrieve
, метод завершит работу в этот момент, возвращая 304 (не измененный) ответ. В противном случае это просто продолжится. Приведенный ниже код показывает это.
Еще одна проблема: даже в используемом вами формате даты (ISO 8601) работает, но это неверно в зависимости от спецификации (раздел 14.25 в http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html , и раздел 3.3.1 в http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1 ), поэтому вам следует рассмотреть возможность использования допустимого формата, чтобы предотвратить будущие проблемы.
Вы можете найти хороший пост о поддержке условного получения в WCF по адресу http://blogs.msdn.com/b/endpoint/archive/2010/02/25/conditional-get-and-etag-support-in-wcf-webhttp-services.aspx.
public class StackOverflow_7919718
{
[ServiceContract]
public class Service
{
[WebGet(ResponseFormat = WebMessageFormat.Json)]
public string GetData()
{
Console.WriteLine("If-Modified-Since header (1): {0}", WebOperationContext.Current.IncomingRequest.IfModifiedSince);
WebOperationContext.Current.IncomingRequest.CheckConditionalRetrieve(DateTime.UtcNow);
return "Data";
}
}
public static void Test()
{
string baseAddress = "http://" Environment.MachineName ":8000/Service";
WebServiceHost host = new WebServiceHost(typeof(Service), new Uri(baseAddress));
host.Open();
Console.WriteLine("Host opened");
Console.WriteLine("Not sending If-Modified-Since header (should return 200):");
Util.SendRequest(baseAddress "/GetData", "GET", null, null);
Console.WriteLine("Sending data in the past, ISO 8601 format (should return 200):");
Util.SendRequest(baseAddress "/GetData", "GET", null, null,
new Dictionary<string, string> { { "If-Modified-Since", "2011-10-25T13:09:39.6242263-04:00" } });
Console.WriteLine("Sending data in the future, ISO 8601 format (should return 304):");
Util.SendRequest(baseAddress "/GetData", "GET", null, null,
new Dictionary<string, string> { { "If-Modified-Since", "2021-10-25T13:09:39.6242263-04:00" } });
Console.WriteLine("Sending data in the past, RFC 1123 format (should return 200):");
Util.SendRequest(baseAddress "/GetData", "GET", null, null,
new Dictionary<string, string> { { "If-Modified-Since", "Wed, 26 Oct 2011 01:00:00 GMT" } });
Console.WriteLine("Sending data in the future, RFC 1123 format (should return 304):");
Util.SendRequest(baseAddress "/GetData", "GET", null, null,
new Dictionary<string, string> { { "If-Modified-Since", "Mon, 27 Oct 2031 10:00:00 GMT" } });
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
public static class Util
{
public static string SendRequest(string uri, string method, string contentType, string body)
{
return SendRequest(uri, method, contentType, body, null);
}
public static string SendRequest(string uri, string method, string contentType, string body, Dictionary<string, string> headers)
{
string responseBody = null;
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(uri);
req.Method = method;
if (headers != null)
{
foreach (string headerName in headers.Keys)
{
switch (headerName)
{
case "If-Modified-Since":
req.IfModifiedSince = DateTime.Parse(headers[headerName]);
break;
default:
req.Headers[headerName] = headers[headerName];
break;
}
}
}
if (!String.IsNullOrEmpty(contentType))
{
req.ContentType = contentType;
}
if (body != null)
{
byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
req.GetRequestStream().Write(bodyBytes, 0, bodyBytes.Length);
req.GetRequestStream().Close();
}
HttpWebResponse resp;
try
{
resp = (HttpWebResponse)req.GetResponse();
}
catch (WebException e)
{
resp = (HttpWebResponse)e.Response;
}
if (resp == null)
{
responseBody = null;
Console.WriteLine("Response is null");
}
else
{
Console.WriteLine("HTTP/{0} {1} {2}", resp.ProtocolVersion, (int)resp.StatusCode, resp.StatusDescription);
foreach (string headerName in resp.Headers.AllKeys)
{
Console.WriteLine("{0}: {1}", headerName, resp.Headers[headerName]);
}
Console.WriteLine();
Stream respStream = resp.GetResponseStream();
if (respStream != null)
{
responseBody = new StreamReader(respStream).ReadToEnd();
Console.WriteLine(responseBody);
}
else
{
Console.WriteLine("HttpWebResponse.GetResponseStream returned null");
}
}
Console.WriteLine();
Console.WriteLine(" *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* ");
Console.WriteLine();
return responseBody;
}
}
Комментарии:
1. Я ценю детали. Я задавался вопросом о формате заголовка if-modified-since. Как вы думаете, в этом причина консоли. Оператор WriteLine в методе getData вашего кода никогда не возвращает значение заголовка (для меня)?
2. Вы говорите, что при запуске этого кода оператор WriteLine на сервере никогда не выводит значение заголовка IMS? Я уже пробовал это на двух машинах, и при первом вызове он ничего не выводит, в то время как в последних 4 он выводит правильное значение.
3. Нет, в моем коде, независимо от вызова и того, что находится в заголовке, я всегда получаю «31.12.1969 7:00:00 PM» для значения свойства IfModifiedSince. Я генерирую запрос от Fiddler. Ваш код при запуске как есть работает, как описано. Я не знаю, в чем разница.
4. Попробуйте посмотреть в Fiddler, чтобы увидеть, в чем разница между запросом, который отправляет мой код, и запросом, который отправляете вы, и посмотреть, сможете ли вы что-то обнаружить.
5. @SonOfPirate, если
IfModifiedSince
свойство запроса равноDateTime
orDateTimeOffset
, и вы всегда получаете 31.12.1969 7:00:00 вечера , то, скорее всегоIf-Modified-Since
, заголовок в запросе не может быть изменен, и он по умолчанию соответствует эпохе в вашем часовом поясе (или что-то в этом роде).