Получение заголовка if-modified-since для работы с WCF

#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 or DateTimeOffset , и вы всегда получаете 31.12.1969 7:00:00 вечера , то, скорее всего If-Modified-Since , заголовок в запросе не может быть изменен, и он по умолчанию соответствует эпохе в вашем часовом поясе (или что-то в этом роде).