aboutsummaryrefslogtreecommitdiff
path: root/Net.Messaging.FBM/src/Server/FBMRequestMessage.cs
blob: ed36571604ff035e4cee6cfdf65af65e0092e84a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/*
* Copyright (c) 2022 Vaughn Nugent
* 
* Library: VNLib
* Package: VNLib.Net.Messaging.FBM
* File: FBMRequestMessage.cs 
*
* FBMRequestMessage.cs is part of VNLib.Net.Messaging.FBM which is part of the larger 
* VNLib collection of libraries and utilities.
*
* VNLib.Net.Messaging.FBM is free software: you can redistribute it and/or modify 
* it under the terms of the GNU Affero General Public License as 
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* VNLib.Net.Messaging.FBM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program.  If not, see https://www.gnu.org/licenses/.
*/

using System;
using System.Text;
using System.Buffers;
using System.Text.Json;
using System.Collections.Generic;

using VNLib.Utils;
using VNLib.Utils.IO;
using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
using VNLib.Utils.Memory.Caching;

namespace VNLib.Net.Messaging.FBM.Server
{
    /// <summary>
    /// Represents a client request message to be serviced
    /// </summary>
    public sealed class FBMRequestMessage : IReusable
    {
        private readonly List<KeyValuePair<HeaderCommand, ReadOnlyMemory<char>>> _headers;
        private readonly int HeaderCharBufferSize;
        /// <summary>
        /// Creates a new resusable <see cref="FBMRequestMessage"/>
        /// </summary>
        /// <param name="headerBufferSize">The size of the buffer to alloc during initialization</param>
        internal FBMRequestMessage(int headerBufferSize)
        {
            HeaderCharBufferSize = headerBufferSize;
            _headers = new();
        }

        private char[]? _headerBuffer;
        
        /// <summary>
        /// The ID of the current message
        /// </summary>
        public int MessageId { get; private set; }
        /// <summary>
        /// Gets the underlying socket-id fot the current connection
        /// </summary>
        public string? ConnectionId { get; private set; }
        /// <summary>
        /// The raw request message, positioned to the body section of the message data
        /// </summary>
        public VnMemoryStream? RequestBody { get; private set; }
        /// <summary>
        /// A collection of headers for the current request
        /// </summary>
        public IReadOnlyList<KeyValuePair<HeaderCommand, ReadOnlyMemory<char>>> Headers => _headers;
        /// <summary>
        /// Status flags set during the message parsing
        /// </summary>
        public HeaderParseError ParseStatus { get; private set; }        
        /// <summary>
        /// The message body data as a <see cref="ReadOnlySpan{T}"/>
        /// </summary>
        public ReadOnlySpan<byte> BodyData => Helpers.GetRemainingData(RequestBody!);

        /// <summary>
        /// Determines if the current message is considered a control frame
        /// </summary>
        public bool IsControlFrame { get; private set; }

        /// <summary>
        /// Prepares the request to be serviced
        /// </summary>
        /// <param name="vms">The request data packet</param>
        /// <param name="socketId">The unique id of the connection</param>
        /// <param name="dataEncoding">The data encoding used to decode header values</param>
        internal void Prepare(VnMemoryStream vms, string socketId, Encoding dataEncoding)
        {
            //Store request body
            RequestBody = vms;
            //Store message id
            MessageId = Helpers.GetMessageId(Helpers.ReadLine(vms));
            //Check mid for control frame
            if(MessageId == Helpers.CONTROL_FRAME_MID)
            {
                IsControlFrame = true;
            }
            else if (MessageId < 1)
            {
                ParseStatus |= HeaderParseError.InvalidId;
                return;
            }

            ConnectionId = socketId;

            //sliding window over remaining data from internal buffer
            ForwardOnlyMemoryWriter<char> writer = new(_headerBuffer);
            
            //Accumulate headers
            while (true)
            {
                //Read the next line from the current stream
                ReadOnlySpan<byte> line = Helpers.ReadLine(vms);
                if (line.IsEmpty)
                {
                    //Done reading headers
                    break;
                }
                HeaderCommand cmd = Helpers.GetHeaderCommand(line);
                //Get header value
                ERRNO charsRead = Helpers.GetHeaderValue(line, writer.Remaining.Span, dataEncoding);
                if (charsRead < 0)
                {
                    //Out of buffer space
                    ParseStatus |= HeaderParseError.HeaderOutOfMem;
                    break;
                }
                else if (!charsRead)
                {
                    //Invalid header
                    ParseStatus |= HeaderParseError.InvalidHeaderRead;
                }
                else
                {
                    //Store header as a read-only sequence
                    _headers.Add(new(cmd, writer.Remaining[..(int)charsRead]));
                    //Shift buffer window
                    writer.Advance(charsRead);
                }
            }
        }
        
        /// <summary>
        /// Deserializes the request body into a new specified object type
        /// </summary>
        /// <typeparam name="T">The type of the object to deserialize</typeparam>
        /// <param name="jso">The <see cref="JsonSerializerOptions"/> to use while deserializing data</param>
        /// <returns>The deserialized object from the request body</returns>
        /// <exception cref="JsonException"></exception>
        public T? DeserializeBody<T>(JsonSerializerOptions? jso = default)
        {
            return BodyData.IsEmpty ? default : BodyData.AsJsonObject<T>(jso);
        }
        /// <summary>
        /// Gets a <see cref="JsonDocument"/> of the request body
        /// </summary>
        /// <returns>The parsed <see cref="JsonDocument"/> if parsed successfully, or null otherwise</returns>
        /// <exception cref="JsonException"></exception>
        public JsonDocument? GetBodyAsJson()
        {
            Utf8JsonReader reader = new(BodyData);
            return JsonDocument.TryParseValue(ref reader, out JsonDocument? jdoc) ? jdoc : default;
        }

        void IReusable.Prepare()
        {
            ParseStatus = HeaderParseError.None;
            //Alloc header buffer
            _headerBuffer = ArrayPool<char>.Shared.Rent(HeaderCharBufferSize);
        }
        
       
        bool IReusable.Release()
        {
            //Dispose the request message
            RequestBody?.Dispose();
            RequestBody = null;
            //Clear headers before freeing buffer
            _headers.Clear();
            //Free header-buffer
            ArrayPool<char>.Shared.Return(_headerBuffer!);
            _headerBuffer = null;
            ConnectionId = null;
            MessageId = 0;
            IsControlFrame = false;
            return true;
        }
    }
}