Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Megjegyzés:
Ez nem a cikk legújabb verziója. Az aktuális kiadásról a cikk .NET 10-es verziójában olvashat.
Figyelmeztetés
A ASP.NET Core ezen verziója már nem támogatott. További információt a .NET és a .NET Core támogatási szabályzatában talál. A jelen cikk .NET 9-es verzióját lásd az aktuális kiadásért .
Készítette : Justin Kotalik
Ez a cikk bemutatja, hogyan olvashat a kérelem törzséből, és hogyan írhat a válasz törzsébe. Ezekhez a műveletekhez kód szükséges lehet köztes szoftver írásakor. A köztes szoftver írásán kívül az egyéni kód általában nem szükséges, mert a műveleteket az MVC és Razor a Pages kezeli.
Két absztrakció van a kérelem- és választestek számára: Stream és Pipe. A kérések olvasása során HttpRequest.Body egy Stream, és HttpRequest.BodyReader egy PipeReader. A válaszíráshoz HttpResponse.Body egy Stream, és HttpResponse.BodyWriter egy PipeWriter.
A folyamatok streameken keresztül ajánlottak. Az adatfolyamok néhány egyszerű művelet esetén könnyebben használhatók, de a pipeline-ok teljesítményelőnyben vannak, és a legtöbb esetben könnyebben alkalmazhatók. ASP.NET Core a belső streamek helyett folyamatokat kezd használni. Ide sorolhatóak például a következők:
FormReaderTextReaderTextWriterHttpResponse.WriteAsync
A streamek nem törlődnek a keretrendszerből. A streamek továbbra is használhatók a .NET-ben:
- Számos adatfolyamtípus nem rendelkezik csőegyenértékekkel, például
FileStreamsésResponseCompression. - Egyszerűen adhat hozzá tömörítést egy streamhez.
Példák streamelése
Tegyük fel, hogy a cél egy köztes szoftver létrehozása, amely a teljes kérelemtörzset sztringek listájaként olvassa fel, új sorokra felosztva. Egy egyszerű stream implementáció a következő példához hasonlóan nézhet ki:
Figyelmeztetés
A következő kód:
- Arra szolgál, hogy bemutassa azokat a problémákat, amelyek a kérés törzsének olvasásához használt pipe (csővezeték) hiányából származnak.
- Éles alkalmazásokban nem használható.
private async Task<List<string>> GetListOfStringsFromStream(Stream requestBody)
{
// Build up the request body in a string builder.
StringBuilder builder = new StringBuilder();
// Rent a shared buffer to write the request body into.
byte[] buffer = ArrayPool<byte>.Shared.Rent(4096);
while (true)
{
var bytesRemaining = await requestBody.ReadAsync(buffer, offset: 0, buffer.Length);
if (bytesRemaining == 0)
{
break;
}
// Append the encoded string into the string builder.
var encodedString = Encoding.UTF8.GetString(buffer, 0, bytesRemaining);
builder.Append(encodedString);
}
ArrayPool<byte>.Shared.Return(buffer);
var entireRequestBody = builder.ToString();
// Split on \n in the string.
return new List<string>(entireRequestBody.Split("\n"));
}
Ha szeretné, hogy a kódkommentárok angolon kívül más nyelvekre is le legyenek fordítva, jelezze nekünk a GitHub vitafórumkérdésénél.
Ez a kód működik, de vannak problémák:
- Mielőtt hozzáfűzné a
StringBuilder-hez, a példa létrehoz egy másik karakterláncot (encodedString), amelyet azonnal eldob. Ez a folyamat a streamben lévő összes bájtra vonatkozóan történik, ezért az eredmény a teljes kérelemtörzs méretének extra memóriafoglalása. - A példa beolvassa a teljes karakterláncot, mielőtt új sorok mentén felosztaná. Hatékonyabb az új sorok keresése a bájttömbben.
Íme egy példa az előző problémák megoldására:
Figyelmeztetés
A következő kód:
- Az előző kód egyes problémáinak megoldását szemlélteti, miközben nem oldja meg az összes problémát.
- Éles alkalmazásokban nem használható.
private async Task<List<string>> GetListOfStringsFromStreamMoreEfficient(Stream requestBody)
{
StringBuilder builder = new StringBuilder();
byte[] buffer = ArrayPool<byte>.Shared.Rent(4096);
List<string> results = new List<string>();
while (true)
{
var bytesRemaining = await requestBody.ReadAsync(buffer, offset: 0, buffer.Length);
if (bytesRemaining == 0)
{
results.Add(builder.ToString());
break;
}
// Instead of adding the entire buffer into the StringBuilder
// only add the remainder after the last \n in the array.
var prevIndex = 0;
int index;
while (true)
{
index = Array.IndexOf(buffer, (byte)'\n', prevIndex);
if (index == -1)
{
break;
}
var encodedString = Encoding.UTF8.GetString(buffer, prevIndex, index - prevIndex);
if (builder.Length > 0)
{
// If there was a remainder in the string buffer, include it in the next string.
results.Add(builder.Append(encodedString).ToString());
builder.Clear();
}
else
{
results.Add(encodedString);
}
// Skip past last \n
prevIndex = index + 1;
}
var remainingString = Encoding.UTF8.GetString(buffer, prevIndex, bytesRemaining - prevIndex);
builder.Append(remainingString);
}
ArrayPool<byte>.Shared.Return(buffer);
return results;
}
Ez a fenti példa:
- Csak akkor nem puffereli a teljes kérelemtörzset,
StringBuilder, ha nincsenek újsoros karakterek. - Nem hívja meg a
Splita karakterláncot.
Azonban még mindig van néhány probléma:
- Ha az új sor karakterek ritkán jelennek meg, a kérelem törzsének nagy része pufferelve lesz egy sztringben.
- A kód továbbra is karakterláncokat (
remainingString) hoz létre, és hozzáadja őket a karakterlánc pufferhez, ami plusz memóriaterület foglalást jelenthet.
Ezek a problémák javíthatók, de a kód egyre bonyolultabbá válik, kevés fejlesztéssel. A folyamatok minimális kódbonyolulással oldják meg ezeket a problémákat.
Csővezetékek
Az alábbi példa bemutatja, hogyan kezelhető az előző streamforgatókönyv egy PipeReaderrel:
private async Task<List<string>> GetListOfStringFromPipe(PipeReader reader)
{
List<string> results = new List<string>();
while (true)
{
ReadResult readResult = await reader.ReadAsync();
var buffer = readResult.Buffer;
SequencePosition? position = null;
do
{
// Look for a EOL in the buffer
position = buffer.PositionOf((byte)'\n');
if (position != null)
{
var readOnlySequence = buffer.Slice(0, position.Value);
AddStringToList(results, in readOnlySequence);
// Skip the line + the \n character (basically position)
buffer = buffer.Slice(buffer.GetPosition(1, position.Value));
}
}
while (position != null);
if (readResult.IsCompleted && buffer.Length > 0)
{
AddStringToList(results, in buffer);
}
reader.AdvanceTo(buffer.Start, buffer.End);
// At this point, buffer will be updated to point one byte after the last
// \n character.
if (readResult.IsCompleted)
{
break;
}
}
return results;
}
private static void AddStringToList(List<string> results, in ReadOnlySequence<byte> readOnlySequence)
{
// Separate method because Span/ReadOnlySpan cannot be used in async methods
ReadOnlySpan<byte> span = readOnlySequence.IsSingleSegment ? readOnlySequence.First.Span : readOnlySequence.ToArray().AsSpan();
results.Add(Encoding.UTF8.GetString(span));
}
Ez a példa számos olyan problémát old meg, amelyek a streamek implementációinak voltak:
- Nincs szükség karakterlánc-pufferre, mert a
PipeReaderkezeli a fel nem használt bájtokat. - A kódolt sztringek közvetlenül hozzáadódnak a visszaadott sztringek listájához.
- A
ToArrayhíváson és a sztring által használt memórián kívül a sztring létrehozása ingyenes.
Amikor közvetlenül ír a HttpResponse.BodyWriter, manuálisan hívja meg a PipeWriter.FlushAsync függvényt, hogy az adatok kiürüljenek az alapul szolgáló választestbe. A következőkért:
-
HttpResponse.BodyWriterolyan,PipeWriteramely puffereli az adatokat, amíg egy kiürítési művelet nem aktiválódik. - A hívás
FlushAsynca pufferelt adatokat az alapjául szolgáló választörzsbe írja.
A fejlesztőn múlik, hogy mikor kell meghívni FlushAsync, kiegyensúlyozási tényezőket, például pufferméretet, hálózati írási többletterhelést, és hogy az adatokat különálló adattömbökben kell-e elküldeni. További információ: System.IO.Pipelines in .NET.
Adapterek
A Body, BodyReader és BodyWriter tulajdonságok elérhetők a HttpRequest és a HttpResponse számára. Ha másik streamre állít be Body , az új adapterek automatikusan egymáshoz igazítják az egyes típusokat. Ha HttpRequest.Body új folyammá van állítva, akkor HttpRequest.BodyReader automatikusan beállításra kerül egy új PipeReader-ra, amely körbefogja HttpRequest.Body.
StartAsync
A HttpResponse.StartAsync mechanizmus azt jelzi, hogy a fejlécek nem módosíthatók, és a OnStarting visszahívások futtatására szolgál. Amikor a Kestrel kiszolgálóként van használva, a StartAsync hívása garantálja, hogy a PipeReader által visszaadott memória a GetMemory belső Kestrel része, nem pedig külső puffer.