yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Ellie HermaszewskaMove switch statement bodies to their own lines (#5493)b118451e3

master
10.7 KiB418 linesraw
1#include "slang-http.h"
2
3#include "slang-process.h"
4#include "slang-string-util.h"
5
6namespace Slang
7{
8
9static const UnownedStringSlice g_headerEnd = UnownedStringSlice::fromLiteral("\r\n\r\n");
10static const UnownedStringSlice g_contentLength = UnownedStringSlice::fromLiteral("Content-Length");
11static const UnownedStringSlice g_contentType = UnownedStringSlice::fromLiteral("Content-Type");
12
13/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! HTTPHeader !!!!!!!!!!!!!!!!!!!!!!! */
14
15void HTTPHeader::reset()
16{
17    const UnownedStringSlice empty;
18
19    m_contentLength = 0;
20    m_mimeType = empty;
21    m_encoding = empty;
22    m_valuePairs.clear();
23    m_header = empty;
24
25    m_arena.deallocateAll();
26}
27
28/* static */ SlangResult HTTPHeader::readHeaderText(BufferedReadStream* stream, Index& outEndIndex)
29{
30    // https://microsoft.github.io/language-server-protocol/specifications/specification-current/
31
32    while (true)
33    {
34        SLANG_RETURN_ON_FAIL(stream->update());
35
36        const Index index = findHeaderEnd(stream);
37        if (index >= 0)
38        {
39            outEndIndex = index;
40            return SLANG_OK;
41        }
42
43        if (stream->isEnd())
44        {
45            return SLANG_FAIL;
46        }
47
48        Process::sleepCurrentThread(0);
49    }
50}
51
52/* static */ Index HTTPHeader::findHeaderEnd(BufferedReadStream* stream)
53{
54    // This could be more efficient - it just searches until there are enough bytes to have
55    // termination
56    auto bytes = stream->getView();
57    UnownedStringSlice input((const char*)bytes.begin(), (const char*)bytes.end());
58
59    const Index index = input.indexOf(g_headerEnd);
60    return (index >= 0) ? (index + g_headerEnd.getLength()) : index;
61}
62
63/* static */ SlangResult HTTPHeader::parse(const UnownedStringSlice& inSlice, HTTPHeader& out)
64{
65    out.reset();
66
67    {
68        auto slice = inSlice;
69        // If has termination at end, remove so we don't have empty lines
70        if (slice.endsWith(g_headerEnd))
71        {
72            slice = slice.head(slice.getLength() - g_headerEnd.getLength());
73        }
74        // Allocate on on the arena, so when we reference other slices, they are part of this
75        // allocation.
76        out.m_header = UnownedStringSlice(
77            out.m_arena.allocateString(slice.begin(), slice.getLength()),
78            slice.getLength());
79    }
80
81    // Okay, we need to split into lines, and then examine the contents
82    for (auto line : LineParser(out.m_header))
83    {
84        // Examine the line for :
85        Index index = line.indexOf(':');
86        if (index < 0)
87        {
88            return SLANG_FAIL;
89        }
90
91        const UnownedStringSlice key = line.head(index).trim();
92        const UnownedStringSlice value = line.tail(index + 1).trim();
93
94        // Add the pair
95        Pair pair{key, value};
96
97        // We could check if key is already used. Some values can be repeated I believe.
98        // So we just allow for now.
99
100        out.m_valuePairs.add(pair);
101
102        if (key == g_contentLength)
103        {
104            Index length;
105            SLANG_RETURN_ON_FAIL(StringUtil::parseInt(value, length) || length < 0);
106
107            out.m_contentLength = length;
108        }
109        else if (key == g_contentType)
110        {
111            List<UnownedStringSlice> slices;
112
113            // text/html; charset=UTF-8
114            StringUtil::split(value, ';', slices);
115
116            if (slices.getCount() < 1)
117            {
118                return SLANG_FAIL;
119            }
120            // set the mime type
121            out.m_mimeType = slices[0].trim();
122
123            // Look for other parameters, in particular charset
124            for (Index i = 1; i < slices.getCount(); ++i)
125            {
126                auto slice = slices[i];
127                Index equalIndex = slice.indexOf('=');
128                if (equalIndex >= 0)
129                {
130                    auto paramName = slice.head(equalIndex).trim();
131                    auto paramValue = slice.tail(equalIndex + 1).trim();
132
133                    if (paramName == UnownedStringSlice::fromLiteral("charset"))
134                    {
135                        out.m_encoding = paramValue;
136                    }
137                }
138            }
139        }
140    }
141
142    return SLANG_OK;
143}
144
145/* static */ SlangResult HTTPHeader::read(BufferedReadStream* stream, HTTPHeader& out)
146{
147    Index endIndex;
148    SLANG_RETURN_ON_FAIL(readHeaderText(stream, endIndex));
149
150    // Get header into a slice
151    UnownedStringSlice headerText((const char*)stream->getBuffer(), endIndex);
152
153    // Parse the slice into the out HttpHeader
154    SLANG_RETURN_ON_FAIL(parse(headerText, out));
155
156    // Can consume these bytes from the stream.
157    stream->consume(endIndex);
158
159    return SLANG_OK;
160}
161
162void HTTPHeader::append(StringBuilder& out) const
163{
164    // Output the content length
165    out << g_contentLength << ": " << SlangSizeT(m_contentLength) << "\r\n";
166
167    // If either is set construct a content type
168    if (m_mimeType.getLength() || m_encoding.getLength())
169    {
170        out << g_contentType << ": ";
171
172        // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
173
174        auto mimeType =
175            m_mimeType.getLength() ? m_mimeType : UnownedStringSlice::fromLiteral("text/plain");
176        auto encoding =
177            m_encoding.getLength() ? m_encoding : UnownedStringSlice::fromLiteral("UTF-8");
178
179        out << mimeType << "; ";
180        out << "charset=" << encoding;
181
182        out << "\r\n";
183    }
184
185    // Output any other data
186    for (auto pair : m_valuePairs)
187    {
188        auto key = pair.key;
189        // Ignore these types, as already output from data we already have
190        if (key == g_contentType || key == g_contentLength)
191        {
192            continue;
193        }
194
195        out << key << ": " << pair.value << "\r\n";
196    }
197
198    // Add termination
199    out << "\r\n";
200}
201
202/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! HTTPPacketConnection !!!!!!!!!!!!!!!!!!!!!!! */
203
204HTTPPacketConnection::HTTPPacketConnection(BufferedReadStream* readStream, Stream* writeStream)
205    : m_readStream(readStream)
206    , m_writeStream(writeStream)
207    , m_readState(ReadState::Header)
208    , m_readResult(SLANG_OK)
209{
210}
211
212SlangResult HTTPPacketConnection::_handleHeader()
213{
214    SLANG_ASSERT(m_readState == ReadState::Header);
215
216    const Index index = HTTPHeader::findHeaderEnd(m_readStream);
217    if (index < 0)
218    {
219        // Don't have the full header yet
220        return SLANG_OK;
221    }
222
223    // Okay we can parse the header
224    UnownedStringSlice slice((const char*)m_readStream->getBuffer(), size_t(index));
225    SLANG_RETURN_ON_FAIL(_updateReadResult(HTTPHeader::parse(slice, m_readHeader)));
226
227    // Consume the header
228    m_readStream->consume(index);
229
230    // We are now consuming content
231    m_readState = ReadState::Content;
232    return SLANG_OK;
233}
234
235SlangResult HTTPPacketConnection::_handleContent()
236{
237    SLANG_ASSERT(m_readState == ReadState::Content);
238    // Do we have enough content, mark as done
239    if (m_readStream->getCount() >= m_readHeader.m_contentLength)
240    {
241        m_readState = ReadState::Done;
242    }
243    return SLANG_OK;
244}
245
246SlangResult HTTPPacketConnection::update()
247{
248    switch (m_readState)
249    {
250    case ReadState::Closed:
251        return SLANG_OK;
252    case ReadState::Error:
253        return m_readResult;
254    default:
255        break;
256    }
257
258    SLANG_RETURN_ON_FAIL(_updateReadResult(m_readStream->update()));
259
260    // Note will only indicate end if the buffer *and* backing stream are end/empty
261    if (m_readStream->isEnd())
262    {
263        if (m_readState == ReadState::Header)
264        {
265            m_readState = ReadState::Closed;
266        }
267        else
268        {
269            // Closed without completing
270            m_readState = ReadState::Error;
271            m_readResult = SLANG_FAIL;
272        }
273        return SLANG_OK;
274    }
275
276    switch (m_readState)
277    {
278    case ReadState::Header:
279        {
280            SLANG_RETURN_ON_FAIL(_handleHeader());
281            // We might be able to progress through content, if we have the header
282            if (m_readState == ReadState::Content)
283            {
284                _handleContent();
285            }
286            break;
287        }
288    case ReadState::Content:
289        {
290            _handleContent();
291            break;
292        }
293    default:
294        break;
295    }
296
297    return m_readResult;
298}
299
300
301namespace
302{ // anonymous
303
304// Handles binary backoff like sleeping mechanism.
305struct SleepState
306{
307    void sleep()
308    {
309        Process::sleepCurrentThread(m_intervalInMs);
310        _update();
311    }
312    void reset()
313    {
314        m_intervalInMs = 0;
315        m_count = 0;
316    }
317    void _update()
318    {
319        const Int maxIntervalInMs = 32;
320        const Int initialCountThreshold = 4;
321
322        ++m_count;
323
324        const Int countThreshold = (m_intervalInMs == 0) ? initialCountThreshold : 1;
325
326        // If we hit the count change the interval
327        if (m_count >= countThreshold)
328        {
329            m_intervalInMs =
330                (m_intervalInMs == 0) ? 1 : Math::Min(m_intervalInMs * 2, maxIntervalInMs);
331            // Reset the count
332            m_count = 0;
333        }
334    }
335
336    Int m_intervalInMs = 0;
337    Int m_count = 0;
338};
339
340} // namespace
341
342SlangResult HTTPPacketConnection::waitForResult(Int timeOutInMs)
343{
344    m_readResult = SLANG_OK;
345
346    int64_t startTick = 0;
347    int64_t timeOutInTicks = -1;
348
349    if (timeOutInMs >= 0)
350    {
351        timeOutInTicks = timeOutInMs * (Process::getClockFrequency() / 1000);
352        startTick = Process::getClockTick();
353    }
354
355    SleepState sleepState;
356
357    while (m_readState == ReadState::Header || m_readState == ReadState::Content)
358    {
359        const auto prevCount = m_readStream->getCount();
360
361        SLANG_RETURN_ON_FAIL(update());
362
363        if (m_readState == ReadState::Done)
364        {
365            break;
366        }
367
368        // We timed out
369        if (timeOutInTicks >= 0 && int64_t(Process::getClockTick()) - startTick >= timeOutInTicks)
370        {
371            break;
372        }
373
374        if (prevCount == m_readStream->getCount())
375        {
376            sleepState.sleep();
377        }
378        else
379        {
380            sleepState.reset();
381        }
382    }
383
384    return m_readResult;
385}
386
387void HTTPPacketConnection::consumeContent()
388{
389    SLANG_ASSERT(m_readState == ReadState::Done);
390    if (m_readState == ReadState::Done)
391    {
392        // Consume the content
393        m_readStream->consume(Index(m_readHeader.m_contentLength));
394        // Back looking for the header again
395        m_readState = ReadState::Header;
396    }
397}
398
399SlangResult HTTPPacketConnection::write(const void* content, size_t sizeInBytes)
400{
401    // Write the header
402    {
403        HTTPHeader header;
404        header.m_contentLength = sizeInBytes;
405
406        StringBuilder buf;
407        header.append(buf);
408
409        SLANG_RETURN_ON_FAIL(m_writeStream->write(buf.getBuffer(), buf.getLength()));
410    }
411
412    // Write the content
413    SLANG_RETURN_ON_FAIL(m_writeStream->write(content, sizeInBytes));
414
415    return SLANG_OK;
416}
417
418} // namespace Slang