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