HttpWebRequest по частям/асинхронный POST

Привет, я хочу загрузить динамически сгенерированный контент в свой веб-API. На клиенте я использую HttpWebRequest. Данные должны загружаться синхронно, и я хочу записать в поток ПОСЛЕ(!) выполнения HTTP-запроса.

(От сервера к клиенту все работает нормально, но от клиента к серверу я получаю некоторые исключения).

Реализация клиента выглядит так:

HttpWebRequest httpWebRequest = HttpWebRequest.Create(myUrl) as HttpWebRequest;
httpWebRequest.Method = "POST";
httpWebRequest.Headers["Authorization"] = "Basic " + ... ;
httpWebRequest.PreAuthenticate = true;

httpWebRequest.SendChunked = true;
//httpWebRequest.AllowWriteStreamBuffering = false; //does not help...
httpWebRequest.ContentType = "application/octet-stream";
Stream st = httpWebRequest.GetRequestStream();

Task<WebResponse> response = httpWebRequest.GetResponseAsync();

// NOW: Write after GetResponse()

var b = Encoding.UTF8.GetBytes("Test1");
st.Write(b, 0, b.Length);

b = Encoding.UTF8.GetBytes("Test2");
st.Write(b, 0, b.Length);

b = Encoding.UTF8.GetBytes("Test3");
st.Write(b, 0, b.Length);

st.Close();

var x = response.Result;
Stream resultStream = x.GetResponseStream();
//do some output...

Я получаю исключения (NotSupportedException: поток не поддерживает параллельные операции чтения или записи ввода-вывода) в stream.write().

Почему я получаю здесь исключения. Иногда первая запись работает, а поздняя запись вызывает исключение. В начале свойство stream.CanWrite имеет значение true, но после первой, второй или третьей записи оно становится ложным... И затем при следующей записи возникает исключение.

Изменить: изменение AllowWriteStreamBuffering не помогло

Приложение: Я нашел свою проблему. Эта проблема вызвана порядком моего кода. Я должен назвать это в следующем порядке:

  • GetRequestStream (асинхронная запись в поток) (запрос отправляется на сервер после первой записи), затем:
  • GetResponseAsync()
  • ПолучитьПотокОтвета()

Я думал, что «GetResponseAsync» запускает клиент для отправки запроса (пока только заголовки). Но это не обязательно, потому что запрос уже отправлен после того, как я запишу первые биты в поток.

Вторая причина моих проблем: Fiddler. (В настоящее время Fiddler поддерживает только потоковую передачу ответов, а не запросов)


person user437899    schedule 28.08.2014    source источник


Ответы (2)


Я нашел свою проблему.

Порядок моего кода вызвал проблему.

Решение состоит в том, чтобы вызвать его в следующем порядке:

  • GetRequestStream (асинхронная запись в поток) (запрос отправляется на сервер после первой записи), затем:
  • GetResponseAsync()
  • ПолучитьПотокОтвета()

Насколько я понимаю, «GetResponseAsync» запускает клиент для отправки запроса (пока только заголовки), но я обнаружил, что это ненужный шаг, потому что запрос уже был отправлен после того, как первые несколько битов были записаны в поток.

Вторая причина моих проблем — Fiddler, но Fiddler поддерживает только потоковую передачу ответов, а не запросов.

Полученный код ссылается на класс HttpWebRequest:

HttpWebRequest httpWebRequest = HttpWebRequest.Create("http://xxx") as HttpWebRequest;
httpWebRequest.Method = "POST";
httpWebRequest.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes("user:pw"));
httpWebRequest.PreAuthenticate = true;    
httpWebRequest.SendChunked = true;
httpWebRequest.AllowWriteStreamBuffering = false;
httpWebRequest.AllowReadStreamBuffering = false;    
httpWebRequest.ContentType = "application/octet-stream";

Stream st = httpWebRequest.GetRequestStream();

Console.WriteLine("Go");

try
{
    st.Write(buffer, 0, buffer.Length); //with the first write, the request will be send.
    st.Write(buffer, 0, buffer.Length);
    st.Write(buffer, 0, buffer.Length);

    for (int i = 1; i <= 10; i++)
    {        
        st.Write(buffer, 0, buffer.Length); //still writing while I can read on the stream at my ASP.NET web api

    }

}
catch (WebException ex)
{
    var y = ex.Response;

}
finally
{
    st.Close();

}

// Now we can read the response from the server in chunks

Task<WebResponse> response = httpWebRequest.GetResponseAsync();

Stream resultStream = response.Result.GetResponseStream();

byte[] data = new byte[1028];

int bytesRead;

while ((bytesRead = resultStream.Read(data, 0, data.Length)) > 0)
{
    string output = System.Text.Encoding.UTF8.GetString(data, 0, bytesRead);

    Console.WriteLine(output);

}

Полученный код ссылается на класс HttpClient:

HttpClientHandler ch = new HttpClientHandler();

HttpClient c = new HttpClient(ch);    
c.DefaultRequestHeaders.TransferEncodingChunked = true;    
c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes("user:pw")));

Stream stream = new MemoryStream();

AsyncStream asyncStream = new AsyncStream(); // Custom implementation of the PushStreamContent with the method, "WriteToStream()".

PushStreamContent streamContent = new PushStreamContent(asyncStream.WriteToStream);

HttpRequestMessage requestMessage = new HttpRequestMessage(new HttpMethod("POST"), "http://XXX") { Content = streamContent };

requestMessage.Headers.TransferEncodingChunked = true;

HttpResponseMessage response = await c.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead);

// The request has been sent, since the first write in the "WriteToStream()" method.

response.EnsureSuccessStatusCode();

Task<Stream> result = response.Content.ReadAsStreamAsync();

byte[] data = new byte[1028];

int bytesRead;

while ((bytesRead = await result.Result.ReadAsync(data, 0, data.Length)) > 0)
{
    string output = System.Text.Encoding.UTF8.GetString(data, 0, bytesRead);

    Console.WriteLine(output);

}

Console.ReadKey();
person user437899    schedule 29.08.2014
comment
Я поражен, что это работает и поддерживается. Можете ли вы опубликовать обновленный код? - person usr; 29.08.2014
comment
Это работает только в идеальных ситуациях и, к сожалению, не является приемлемым вариантом. Клиент достаточно упрям, чтобы продолжать писать весь свой поток, даже если сервер долго ошибался и отправлял обратно HTTP-статус. Клиент не будет читать ничего, отправленного сервером, если он не завершит свой запрос, что удивительно, даже после закрытия пула приложений, IIS и базового сокета. Я попытался перейти на один уровень вниз к HttpWebRequest, чтобы попытаться прочитать поток, пока клиент все еще пишет, однако эта операция не разрешена. - person MoonStom; 16.12.2015
comment
@MoonStom: если это проблема, вам удалось найти способ ее обойти? - person Luke Baulch; 04.07.2016
comment
@Luke Baulch: Нет, вот случай с MS Connect: a-response-from-the-server" rel="nofollow noreferrer">connect.microsoft.com/VisualStudio/feedback/details/2160559/ - person MoonStom; 04.07.2016
comment
К сожалению Коннект мертв. - person Marc L.; 26.04.2018

Вы используете объекты HttpWebRequest одновременно в нескольких потоках. response — это задача, которая выполняется одновременно с вашими операциями записи. Это явно небезопасно для потоков.

Кроме того, я не вижу, чего вы хотите добиться. HTTP использует модель «запрос-ответ». Сервер не может (обычно) отправить один байт ответа до получения всего запроса. Теоретически это возможно. Но это очень необычно и, скорее всего, не поддерживается .NET BCL. Это будет поддерживаться только вашим (очень необычным) настраиваемым HTTP-сервером.

person usr    schedule 29.08.2014
comment
Да, я знаю, что это необычный случай. :-) Объяснение моей цели: в основном я хочу только возвращать динамически сгенерированные данные с сервера клиенту. Для этого я использую кодировку передачи: chunked. Клиент получает ответ, а сервер все еще может записывать в поток. (При этом я могу предложить клиенту что-то вроде yield return) Моя следующая попытка состояла в том, чтобы сделать это от клиента к серверу (мой вопрос о стеке). Но: Конечно, генерация результата не может начаться до того, как клиент отправит последние биты. (Кстати: я нашел свою проблему, см. приложение) - person user437899; 29.08.2014