summaryrefslogtreecommitdiffstats
path: root/source/core/slang-http.h
blob: 784357273a1ca9efd2094ba22bb7082b16c46572 (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
#ifndef SLANG_CORE_HTTP_H
#define SLANG_CORE_HTTP_H

#include "../../slang.h"

#include "slang-string.h"
#include "slang-list.h"

#include "slang-memory-arena.h"

#include "slang-stream.h"

#include "../../slang-com-helper.h"
#include "../../slang-com-ptr.h"

namespace Slang {

/// All of the contained UnownedStringSlice can be stored in m_header. This can be checked via testing if
/// the memory overlaps.
///
/// The m_arena can be used to store slices in an ad-hoc manner to keep in scope with the Header.
struct HTTPHeader
{
    struct Pair
    {
        UnownedStringSlice key;
        UnownedStringSlice value;
    };

        /// Append the header (including termination) to out
    void append(StringBuilder& out) const;

        /// Reset the contents
    void reset();

    SLANG_INLINE Index indexOfKey(const UnownedStringSlice& slice) const;

        /// Ctor
    HTTPHeader() :
        m_arena(1024)
    {
    }

        /// Reads from stream until the buffer contains all of the header. The outEndIndex will point
        /// past the header termination.
    static SlangResult readHeaderText(BufferedReadStream* stream, Index& outEndIndex);

        /// Returns the index of the end of the header (index of first byte *after* the header), or < if doesn't have an end
    static Index findHeaderEnd(BufferedReadStream* stream);

        /// Parse the slice (holding a header) into out.
        /// Will allocate the slice on the array and store in m_header.
        /// Slices will reference sections of m_header, that may be useful in some scenarios.
    static SlangResult parse(const UnownedStringSlice& slice, HTTPHeader& out);

        /// Read from buffered stream header, and place parsed header into out
    static SlangResult read(BufferedReadStream* stream, HTTPHeader& out);

    size_t m_contentLength;             ///< Content length in bytes

    UnownedStringSlice m_mimeType;      ///< The mime type
    UnownedStringSlice m_encoding;      ///< The character encoding

    UnownedStringSlice m_header;        ///< Optionally holds the whole of the header 

    List<Pair> m_valuePairs;            /// All of the value pairs

    MemoryArena m_arena;                ///< Used to store backing memory

private:
    // Disable
    HTTPHeader(const HTTPHeader&) = delete;
    void operator=(const HTTPHeader&) = delete;
};

// -----------------------------------------------------------------
Index HTTPHeader::indexOfKey(const UnownedStringSlice& slice) const
{
    return m_valuePairs.findFirstIndex([&](const HTTPHeader::Pair& pair) -> bool { return pair.key == slice; });
}

/// Implements a way to communicate over Streams via the HTTP *protocol*.
///
/// Allows for reading without blocking, via calls to 'update'. When a complete
/// HTTP 'packet' (combination of header and content) is available, the ReadState will
/// become 'Done'. For this to work without blocking it relies on the stream backing the BufferedReadStream
/// to be non blocking.
///
/// If it is only necessary to respond on complete packets 'waitForContent' can be used.
/// If this returns and ReadState is Done, then getHeader holds the current header, and getContent
/// holds the content of the 'packet'.
///
/// Once the packet has been processed 'consumeContent' can be used. Once consumeContent is called
/// both contents of getContent and getReadHeader will no longer be valid.
///
/// Ie using the slice returned from getContent *after* consumeContent is called is *undefined behavior*.
/// 
/// NOTE! that this does not implement HTTP over TCP/IP.
/// That said it could be used to communicate via the HTTP protocol over TCP/IP
/// if the Streams supplied were TCP/IP sockets.
class HTTPPacketConnection : public RefObject
{
public:

    enum class ReadState
    {
        Header,                     ///< Reading reader
        Content,                    ///< Reading content (ie header is read)
        Done,                       ///< The content is read
        Closed,                     ///< The read stream is closed - no further packets can be read
        Error,                      ///< In an error state - no further packets can be read
    };

        /// Update state
    SlangResult update();
        /// Get the current read staet
    ReadState getReadState() const { return m_readState; }
        /// Get the read header
    const HTTPHeader& getReadHeader() const { SLANG_ASSERT(hasHeader()); return m_readHeader; }
        /// Get the content 
    ConstArrayView<Byte> getContent() const { SLANG_ASSERT(m_readState == ReadState::Done); return ConstArrayView<Byte>((const Byte*)m_readStream->getBuffer(), m_readHeader.m_contentLength); }

        /// Write. Will potentially block if write stream is blocking.
    SlangResult write(const void* content, size_t sizeInBytes);

        /// Blocks until some result - a packet, closure, or some kind of error or timeout.
        /// TimeOut of -1 means no timeout.
    SlangResult waitForResult(Int timeOutInMs = -1);
        /// Consume the content - so can read next content
    void consumeContent();

        /// True if connection is active.
    bool isActive() const { return m_readState != ReadState::Error && m_readState != ReadState::Closed;  }

    bool hasHeader() const { return m_readState == ReadState::Content || m_readState == ReadState::Done;  }
        /// True if has content (implies has header)
    bool hasContent() const { return m_readState == ReadState::Done;  }

        /// Ctor
    HTTPPacketConnection(BufferedReadStream* readStream, Stream* writeStream);

protected:
    SlangResult _updateReadResult(SlangResult res)
    {
        if (SLANG_FAILED(res) && SLANG_SUCCEEDED(m_readResult))
        {
            m_readState = ReadState::Error;
            m_readResult = res;
        }
        return res;
    }

    SlangResult _handleHeader();
    SlangResult _handleContent();

    SlangResult m_readResult;
    HTTPHeader m_readHeader;

    ReadState m_readState;

    RefPtr<BufferedReadStream> m_readStream;
    RefPtr<Stream> m_writeStream;
};

} // namespace Slang

#endif // SLANG_CORE_HTTP_H