1 #ifndef CONNECT___NCBI_HTTP_SESSION__HPP
2 #define CONNECT___NCBI_HTTP_SESSION__HPP
3 
4 /* $Id: ncbi_http_session.hpp 612999 2020-07-30 19:22:55Z sadyrovr $
5  * ===========================================================================
6  *
7  *                            PUBLIC DOMAIN NOTICE
8  *               National Center for Biotechnology Information
9  *
10  *  This software/database is a "United States Government Work" under the
11  *  terms of the United States Copyright Act.  It was written as part of
12  *  the author's official duties as a United States Government employee and
13  *  thus cannot be copyrighted.  This software/database is freely available
14  *  to the public for use. The National Library of Medicine and the U.S.
15  *  Government have not placed any restriction on its use or reproduction.
16  *
17  *  Although all reasonable efforts have been taken to ensure the accuracy
18  *  and reliability of the software and data, the NLM and the U.S.
19  *  Government do not and cannot warrant the performance or results that
20  *  may be obtained by using this software or data. The NLM and the U.S.
21  *  Government disclaim all warranties, express or implied, including
22  *  warranties of performance, merchantability or fitness for any particular
23  *  purpose.
24  *
25  *  Please cite the author in any work or product based on this material.
26  *
27  * ===========================================================================
28  *
29  * Author:  Aleksey Grichenko
30  *
31  * File Description:
32  *   CHttpSession class supporting different types of requests/responses,
33  *   headers/cookies/sessions management etc.
34  *
35  */
36 
37 #include <corelib/ncbi_cookies.hpp>
38 #include <connect/ncbi_conn_stream.hpp>
39 
40 
41 /** @addtogroup HttpSession
42  *
43  * @{
44  */
45 
46 
47 BEGIN_NCBI_SCOPE
48 
49 
50 // Forward declarations for shortcut functions.
51 class CHttpResponse;
52 class CHttpHeaders;
53 
54 
55 // Default retries value.
56 struct SGetHttpDefaultRetries
57 {
58     unsigned short operator()(void) const;
59 };
60 
61 /// Nullable retries for CHttpRequest
62 typedef CNullable<unsigned short, SGetHttpDefaultRetries> THttpRetries;
63 
64 
65 /// Shortcut for GET request. Each request uses a separate session,
66 /// no data like cookies is shared between multiple requests.
67 /// @sa CHttpSession::Get()
68 NCBI_XCONNECT_EXPORT
69 CHttpResponse g_HttpGet(const CUrl&     url,
70                         const CTimeout& timeout = CTimeout(CTimeout::eDefault),
71                         THttpRetries    retries = null);
72 
73 /// Shortcut for GET request with custom headers. Each request uses a separate
74 /// session, no data like cookies is shared between multiple requests.
75 /// @sa CHttpSession::Get()
76 NCBI_XCONNECT_EXPORT
77 CHttpResponse g_HttpGet(const CUrl&         url,
78                         const CHttpHeaders& headers,
79                         const CTimeout&     timeout = CTimeout(CTimeout::eDefault),
80                         THttpRetries        retries = null);
81 
82 /// Shortcut for POST request. Each request uses a separate session,
83 /// no data like cookies is shared between multiple requests.
84 /// @sa CHttpSession::Post()
85 NCBI_XCONNECT_EXPORT
86 CHttpResponse g_HttpPost(const CUrl&     url,
87                          CTempString     data,
88                          CTempString     content_type = CTempString(),
89                          const CTimeout& timeout = CTimeout(CTimeout::eDefault),
90                          THttpRetries    retries = null);
91 
92 /// Shortcut for POST request with custom headers. Each request uses a separate
93 /// session, no data like cookies is shared between multiple requests.
94 /// @sa CHttpSession::Post()
95 NCBI_XCONNECT_EXPORT
96 CHttpResponse g_HttpPost(const CUrl&         url,
97                          const CHttpHeaders& headers,
98                          CTempString         data,
99                          CTempString         content_type = CTempString(),
100                          const CTimeout&     timeout = CTimeout(CTimeout::eDefault),
101                          THttpRetries        retries = null);
102 
103 /// Shortcut for PUT request. Each request uses a separate session,
104 /// no data like cookies is shared between multiple requests.
105 /// @sa CHttpSession::Put()
106 NCBI_XCONNECT_EXPORT
107 CHttpResponse g_HttpPut(const CUrl&     url,
108                         CTempString     data,
109                         CTempString     content_type = CTempString(),
110                         const CTimeout& timeout = CTimeout(CTimeout::eDefault),
111                         THttpRetries    retries = null);
112 
113 /// Shortcut for PUT request with custom headers. Each request uses a separate
114 /// session, no data like cookies is shared between multiple requests.
115 /// @sa CHttpSession::Put()
116 NCBI_XCONNECT_EXPORT
117 CHttpResponse g_HttpPut(const CUrl&         url,
118                         const CHttpHeaders& headers,
119                         CTempString         data,
120                         CTempString         content_type = CTempString(),
121                         const CTimeout&     timeout = CTimeout(CTimeout::eDefault),
122                         THttpRetries        retries = null);
123 
124 class CHttpSession_Base;
125 
126 
127 class NCBI_XCONNECT_EXPORT CHttpHeaders : public CObject
128 {
129 public:
130     /// Create empty headers list.
CHttpHeaders(void)131     CHttpHeaders(void) {}
132 
133     /// Some standard HTTP headers.
134     enum EHeaderName {
135         eCacheControl = 0,
136         eContentLength,
137         eContentType,
138         eCookie,
139         eDate,
140         eExpires,
141         eLocation,
142         eRange,
143         eReferer,
144         eSetCookie,
145         eUserAgent,
146         eHost
147     };
148 
149     /// Helper class allowing to use both strings and enums as header names.
150     /// The class should not be used explicitly - all conversions are automatic.
151     class CHeaderNameConverter {
152     public:
CHeaderNameConverter(const char * name)153         CHeaderNameConverter(const char* name)
154             : m_Name(name) {}
CHeaderNameConverter(const string & name)155         CHeaderNameConverter(const string& name)
156             : m_Name(name) {}
CHeaderNameConverter(CTempString name)157         CHeaderNameConverter(CTempString name)
158             : m_Name(name) {}
CHeaderNameConverter(CHttpHeaders::EHeaderName name)159         CHeaderNameConverter(CHttpHeaders::EHeaderName name)
160             : m_Name(CHttpHeaders::GetHeaderName(name)) {}
GetName(void)161         CTempString GetName(void) { return m_Name; }
162     private:
163         CTempString m_Name;
164     };
165 
166     /// List of header values (may be required for headers with multiple values
167     /// like Cookie).
168     typedef vector<string> THeaderValues;
169     /// Map of header name-values, case-insensitive.
170     typedef map<string, THeaderValues, PNocase> THeaders;
171 
172     /// Check if there's at least one header with the name.
173     bool HasValue(CHeaderNameConverter name) const;
174 
175     /// Get number of values with the given name.
176     size_t CountValues(CHeaderNameConverter name) const;
177 
178     /// Get value of the header or empty string. If multiple values
179     /// exist, return the last one.
180     const string& GetValue(CHeaderNameConverter name) const;
181 
182     /// Get all values with the given name.
183     const THeaderValues& GetAllValues(CHeaderNameConverter name) const;
184 
185     /// Remove all existing values with the name, set the new value.
186     /// Throw exception if the name is a reserved one and can not be
187     /// set directly (NCBI-SID, NCBI-PHID). These values should be
188     /// set through CRequestContext, the headers will be added by
189     /// CConn_HttpStream automatically.
190     void SetValue(CHeaderNameConverter name, CTempString value);
191 
192     /// Add new value with the name (multiple values are allowed with
193     /// the same name, the order is preserved).
194     /// Throw exception if the name is a reserved one and can not be
195     /// set directly (NCBI-SID, NCBI-PHID). These values should be
196     /// set through CRequestContext, the headers will be added by
197     /// CConn_HttpStream automatically.
198     void AddValue(CHeaderNameConverter name, CTempString value);
199 
200     /// Remove all values with the given name.
201     void Clear(CHeaderNameConverter name);
202 
203     /// Remove all headers.
204     void ClearAll(void);
205 
206     /// Clear any existing values and copy all headers from 'headers'
207     /// to this object.
208     void Assign(const CHttpHeaders& headers);
209 
210     /// Add values from 'headers' to this object. When merging values
211     /// with the same name, the new values are added after the existing
212     /// ones, so that the new values have higher priority.
213     void Merge(const CHttpHeaders& headers);
214 
~CHttpHeaders(void)215     virtual ~CHttpHeaders(void) {}
216 
217 private:
218     friend class CHttpResponse;
219     friend class CHttpRequest;
220 
221     // Prohibit copying headers.
222     CHttpHeaders(const CHttpHeaders&);
223     CHttpHeaders& operator=(const CHttpHeaders&);
224 
225     // Check if the name is a reserved one (NCBI-SID, NCBI-PHID).
226     // If yes, log error - these headers must be set in
227     // CRequestContext, not directly.
228     // Return 'false' if the header name is not reserved and a value can
229     // be set safely.
230     bool x_IsReservedHeader(CTempString name) const;
231 
232     THeaders            m_Headers;
233 
234 public:
235     /// Parse headers from the string (usually provided by a stream callback).
236     /// The new headers are added to the existing ones.
237     void ParseHttpHeader(const CTempString& headers);
238 
239     /// Get all headers as a single string as required by CConn_HttpStream.
240     /// Each header line is terminated by a single HTTP_EOL.
241     string GetHttpHeader(void) const;
242 
Get() const243     const THeaders& Get() const { return m_Headers; }
244 
245     /// Get string representation of the given name.
246     static const char* GetHeaderName(EHeaderName name);
247 };
248 
249 
250 /// Interface for custom form data providers.
251 class NCBI_XCONNECT_EXPORT CFormDataProvider_Base : public CObject
252 {
253 public:
254     /// Get content type. Returns empty string by default, indicating
255     /// no Content-Type header should be used for the part.
GetContentType(void) const256     virtual string GetContentType(void) const { return string(); }
257 
258     /// Get optional filename to be shown in Content-Disposition header.
GetFileName(void) const259     virtual string GetFileName(void) const { return string(); }
260 
261     /// Write user data to the stream.
262     virtual void WriteData(CNcbiOstream& out) const = 0;
263 
~CFormDataProvider_Base(void)264     virtual ~CFormDataProvider_Base(void) {}
265 };
266 
267 
268 /// POST request data
269 class NCBI_XCONNECT_EXPORT CHttpFormData : public CObject
270 {
271 public:
272     /// Supported content types for POST requests.
273     enum EContentType {
274         eFormUrlEncoded,      ///< 'application/x-www-form-urlencoded', default
275         eMultipartFormData    ///< 'multipart/form-data'
276     };
277 
278     /// Add name=value pair. The data of this type can be sent using either
279     /// eFormUrlEncoded or eMultipartFormData content types.
280     /// @param entry_name
281     ///   Name of the form input. The name must not be empty, otherwise an
282     ///   exception is thrown. Multiple values with the same name are allowed.
283     /// @param value
284     ///   Value for the input. If the form content type is eFormUrlEncoded,
285     ///   the value will be URL encoded properly. Otherwise the value is sent as-is.
286     ///   URL-encoded form data allows only one value per entry, otherwise
287     ///   exception will be thrown when sending the data.
288     /// @param content_type
289     ///   Content type for the entry used if the form content type is
290     ///   eMultipartFormData. If not set, the protocol assumes 'text/plain'
291     ///   content type. Not used when sending eFormUrlEncoded content.
292     void AddEntry(CTempString entry_name,
293                   CTempString value,
294                   CTempString content_type = CTempString());
295 
296     /// Add file entry. The form content type is automatically set to
297     /// eMultipartFormData and can not be changed later.
298     /// @param entry_name
299     ///   Name of the form input responsible for selecting files. Multiple
300     ///   files can be added with the same entry name. The name must not be
301     ///   empty, otherwise an exception is thrown.
302     /// @param file_name
303     ///   Path to the local file to be sent. The file must exist and be
304     ///   readable during the call to WriteFormData, otherwise an exception
305     ///   will be thrown.
306     /// @param content_type
307     ///   Can be used to set content type for each file. If not set, the
308     ///   protocol assumes it to be 'application/octet-stream'.
309     void AddFile(CTempString entry_name,
310                  CTempString file_name,
311                  CTempString content_type = CTempString());
312 
313     /// Add custom data provider. The data written by the provider is
314     /// properly prefixed with Content-Disposition, boundary, Content-Type etc.
315     /// The form content type is automatically set to eMultipartFormData and
316     /// can not be changed later.
317     /// @param entry_name
318     ///   Name of the form input responsible for the data. Multiple providers
319     ///   can be added with the same entry name. The name must not be empty,
320     ///   otherwise an exception is thrown.
321     /// @param provider
322     ///   An instance of CFormDataProvider_Base derived class. The object must
323     ///   be created in heap.
324     void AddProvider(CTempString             entry_name,
325                      CFormDataProvider_Base* provider);
326 
327     /// Check if the form data is empty (no entries have been added).
328     bool IsEmpty(void) const;
329 
330     /// Clear all form data, reset content type to the default eFormUrlEncoded.
331     void Clear(void);
332 
333     /// Write form data to the stream using the selected form content type.
334     /// If the data is not valid (e.g. a file does not exist or can not be
335     /// read), throw CHttpSessionException.
336     void WriteFormData(CNcbiOstream& out) const;
337 
338     /// Get current content type.
GetContentType(void) const339     EContentType GetContentType(void) const { return m_ContentType; }
340     /// Set content type for the form data. If the new content type conflicts
341     /// with the types of entries already added, throw an exception (e.g.
342     /// files/providers require eMultipartFormData content type).
343     /// @note Content types for individual entries can be set when adding
344     /// an entry (see AddEntry, AddFile, AddProvider).
345     void SetContentType(EContentType content_type);
346 
~CHttpFormData(void)347     virtual ~CHttpFormData(void) {}
348 
349 private:
350     friend class CHttpRequest;
351 
352     // Create new data object. The default content type is eFormUrlEncoded.
353     CHttpFormData(void);
354 
355     // Prohibit copying.
356     CHttpFormData(const CHttpFormData&);
357     CHttpFormData& operator=(const CHttpFormData&);
358 
359     struct SFormData {
360         string m_Value;
361         string m_ContentType;
362     };
363 
364     typedef vector<SFormData>   TValues;
365     typedef map<string, TValues> TEntries;
366     typedef vector< CRef<CFormDataProvider_Base> > TProviders;
367     typedef map<string, TProviders> TProviderEntries;
368 
369     EContentType     m_ContentType;
370     TEntries         m_Entries;   // Normal entries
371     TProviderEntries m_Providers; // Files and custom providers
372     mutable string   m_Boundary;  // Main boundary string
373 
374 public:
375     /// Get the form content type as a string.
376     string GetContentTypeStr(void) const;
377 
378     /// Generate a new random string to be used as multipart boundary.
379     static string CreateBoundary(void);
380 };
381 
382 
383 /// HTTP response
384 class NCBI_XCONNECT_EXPORT CHttpResponse : public CObject
385 {
386 public:
387     /// Get incoming HTTP headers.
Headers(void) const388     const CHttpHeaders& Headers(void) const { return *m_Headers; }
389 
390     /// Get input stream. If the status code indicates that there's
391     /// no content to be read, throw CHttpSessionException. The actual
392     /// request body (e.g. error page) can be read using ErrorStream().
393     CNcbiIstream& ContentStream(void) const;
394 
395     /// Get input stream containing error message (e.g. 404 page)
396     /// If the status code indicates that valid content is available
397     /// the method throws CHttpSessionException.
398     CNcbiIstream& ErrorStream(void) const;
399 
400     /// Get actual resource location. This may be different from the
401     /// requested URL in case of redirects.
GetLocation(void) const402     const CUrl& GetLocation(void) const { return m_Location; }
403 
404     /// Get response status code.
GetStatusCode(void) const405     int GetStatusCode(void) const { return m_StatusCode; }
406 
407     /// Get response status text.
GetStatusText(void) const408     const string& GetStatusText(void) const { return m_StatusText; }
409 
410     /// Check if the requested content can be read from the content stream.
411     /// This is true for status codes 2xx. If there's no content available,
412     /// ContentStream() will throw an exception and response body can be
413     /// accessed using ErrorStream().
414     /// @note This method returns true even if the content stream is empty
415     /// (e.g. after HEAD request), it's based on status code only.
416     bool CanGetContentStream(void) const;
417 
~CHttpResponse(void)418     virtual ~CHttpResponse(void) {}
419 
420 private:
421     friend class CHttpRequest;
422 
423     CHttpResponse(CHttpSession_Base& session, const CUrl& url, shared_ptr<iostream> stream = {});
424 
425     // Update response headers and location, parse cookies and store them in the session.
426     void x_Update(CHttpHeaders::THeaders headers, int status_code, string status_text);
427 
428     CRef<CHttpSession_Base> m_Session;
429     CUrl                    m_Url;      // Original URL
430     CUrl                    m_Location; // Redirection or the original URL.
431     shared_ptr<iostream>    m_Stream;
432     CRef<CHttpHeaders>      m_Headers;
433     int                     m_StatusCode;
434     string                  m_StatusText;
435 };
436 
437 
438 /// HTTP request
439 class NCBI_XCONNECT_EXPORT CHttpRequest
440 {
441 public:
442     /// Get HTTP headers to be sent.
Headers(void)443     CHttpHeaders& Headers(void) { return *m_Headers; }
444 
445     /// Get form data to be sent with POST request. Throw an exception
446     /// if the selected method is not POST or if the request is already
447     /// being executed (after calling ContentStream() but before Execute()).
448     CHttpFormData& FormData(void);
449 
450     /// Get output stream to write user data.
451     /// Headers are sent automatically when opening the stream.
452     /// No changes can be made to the request after getting the stream
453     /// until it is completed by calling Execute().
454     /// Throws an exception if the selected request method does not
455     /// support sending data or if the existing form data is not empty.
456     /// @note This method automatically adds cookies to the request headers.
457     CNcbiOstream& ContentStream(void);
458 
459     /// Send the request, initialize and return the response. The output
460     /// content stream is flushed and closed by this call.
461     /// After executing a request it can be modified (e.g. more headers
462     /// and/or form data added) and re-executed.
463     /// @note This method automatically adds cookies to the request headers.
464     CHttpResponse Execute(void);
465 
466     /// Get current timeout. If set to CTimeout::eDefault, the global
467     /// default value is used (or the one from $CONN_TIMEOUT).
GetTimeout(void) const468     const CTimeout& GetTimeout(void) const { return m_Timeout; }
469     /// Set new timeout.
470     CHttpRequest& SetTimeout(const CTimeout& timeout);
471     /// Set new timeout in seconds/microseconds.
472     CHttpRequest& SetTimeout(unsigned int sec, unsigned int usec = 0);
473 
474     /// Get number of retries. If not set returns the global default
475     /// value ($CONN_MAX_TRY - 1).
GetRetries(void) const476     THttpRetries GetRetries(void) const { return m_Retries; }
477     /// Set number of retries. If not set, the global default
478     /// value is used ($CONN_MAX_TRY - 1).
SetRetries(THttpRetries retries)479     void SetRetries(THttpRetries retries) { m_Retries = retries; }
480 
481     /// Get current deadline for Execute().
482     /// For now, effective only for if waiting for actual response
483     /// (as opposed to a recognized 'retry later' response), infinite by default.
484     /// @sa Execute() GetRetryProcessing()
GetDeadline() const485     const CTimeout& GetDeadline() const { return m_Deadline; }
486     /// Set new deadline for Execute().
487     /// @sa Execute() SetRetryProcessing()
488     CHttpRequest& SetDeadline(const CTimeout& deadline);
489 
490     /// Return whether Execute() will wait for actual response.
491     /// If on, will wait for deadline expired or actual response
492     /// (as opposed to a recognized 'retry later' response), off by default.
493     /// @sa Execute() GetDeadline()
GetRetryProcessing() const494     ESwitch GetRetryProcessing() const { return m_RetryProcessing; }
495     /// Set whether Execute() should wait for actual response.
496     /// @sa Execute() SetDeadline()
497     CHttpRequest& SetRetryProcessing(ESwitch on_off);
498 
499     /// Set callback to adjust URL after resolving service location.
500     /// The callback must take a CUrl reference and return bool:
501     /// bool AdjustUrlCallback(CUrl& url);
502     /// The callback should return true for the adjusted URL to be used to
503     /// make the request, or false if the changes should be discarded.
504     template<class TCallback>
SetAdjustUrlCallback(TCallback callback)505     void SetAdjustUrlCallback(TCallback callback) {
506         m_AdjustUrl.Reset(new CAdjustUrlCallback<TCallback>(callback));
507     }
508 
509 private:
510     friend class CHttpSession_Base;
511     friend class CHttpSessionImpl1x;
512     friend class CHttpSessionImpl2;
513 
514     CHttpRequest(CHttpSession_Base& session, const CUrl& url, EReqMethod method);
515 
516     class CAdjustUrlCallback_Base : public CObject {
517     public:
~CAdjustUrlCallback_Base(void)518         virtual ~CAdjustUrlCallback_Base(void) {}
519         virtual bool AdjustUrl(CUrl& url) = 0;
520     };
521 
522     template<class TCallback>
523     class CAdjustUrlCallback : public CAdjustUrlCallback_Base {
524     public:
CAdjustUrlCallback(TCallback & callback)525         CAdjustUrlCallback(TCallback& callback) : m_Callback(callback) {}
AdjustUrl(CUrl & url)526         virtual bool AdjustUrl(CUrl& url) { return m_Callback(url); }
527     private:
528         TCallback m_Callback;
529     };
530 
531     // Open connection, initialize response.
532     void x_InitConnection(bool use_form_data);
533     void x_InitConnection2(shared_ptr<iostream> stream, bool is_service);
534 
535     bool x_CanSendData(void) const;
536 
537     // Find cookies matching the url, add or replace 'Cookie' header with the
538     // new values.
539     void x_AddCookieHeader(const CUrl& url, bool initial);
540 
541     void x_AdjustHeaders(bool use_form_data);
542     void x_UpdateResponse(CHttpHeaders::THeaders headers, int status_code, string status_text);
543 
544     // CConn_HttpStream callback for parsing headers.
545     // 'user_data' must point to a CHttpRequest object.
546     static EHTTP_HeaderParse sx_ParseHeader(const char*, void*, int);
547     // CConn_HttpStream callback for handling retries and redirects.
548     // 'user_data' must point to a CHttpRequest object.
549     static int sx_Adjust(SConnNetInfo* net_info,
550                          void*         user_data,
551                          unsigned int  failure_count);
552 
553     CRef<CHttpSession_Base> m_Session;
554     CUrl                m_Url;
555     bool                m_IsService;
556     EReqMethod          m_Method;
557     CRef<CHttpHeaders>  m_Headers;
558     CRef<CHttpFormData> m_FormData;
559     shared_ptr<iostream> m_Stream;
560     CRef<CHttpResponse> m_Response; // current response or null
561     CTimeout            m_Timeout;
562     THttpRetries        m_Retries;
563     CTimeout            m_Deadline;
564     ESwitch             m_RetryProcessing;
565     CRef<CAdjustUrlCallback_Base> m_AdjustUrl;
566 };
567 
568 
569 /// HTTP session class, holding common data for multiple requests.
570 class NCBI_XCONNECT_EXPORT CHttpSession_Base : public CObject,
571                                           virtual protected CConnIniter
572 {
573 public:
574     /// Supported request methods, proxy for EReqMethod.
575     /// @sa EReqMethod
576     enum ERequestMethod {
577         eHead   = eReqMethod_Head,
578         eGet    = eReqMethod_Get,
579         ePost   = eReqMethod_Post,
580         ePut    = eReqMethod_Put,
581 		ePatch  = eReqMethod_Patch,
582         eDelete = eReqMethod_Delete
583     };
584 
585     /// Initialize request. This does not open connection to the server.
586     /// A user can set headers/form-data before opening the stream and
587     /// sending the actual request.
588     /// The default request method is GET.
589     CHttpRequest NewRequest(const CUrl& url, ERequestMethod method = eGet);
590 
591     /// Shortcut for GET requests.
592     /// @param url
593     ///   URL to send request to.
594     /// @sa NewRequest() CHttpRequest
595     CHttpResponse Get(const CUrl&     url,
596                       const CTimeout& timeout = CTimeout(CTimeout::eDefault),
597                       THttpRetries    retries = null);
598 
599     /// Shortcut for POST requests.
600     /// @param url
601     ///   URL to send request to.
602     /// @param data
603     ///   Data to be sent with the request. The data is sent as-is,
604     ///   any required encoding must be performed by the caller.
605     /// @param content_type
606     ///   Content-type. If empty, application/x-www-form-urlencoded
607     ///   is used.
608     /// @sa NewRequest() CHttpRequest
609     CHttpResponse Post(const CUrl&     url,
610                        CTempString     data,
611                        CTempString     content_type = CTempString(),
612                        const CTimeout& timeout = CTimeout(CTimeout::eDefault),
613                        THttpRetries    retries = null);
614 
615     /// Shortcut for PUT requests.
616     /// @param url
617     ///   URL to send request to.
618     /// @param data
619     ///   Data to be sent with the request. The data is sent as-is,
620     ///   any required encoding must be performed by the caller.
621     /// @param content_type
622     ///   Content-type. If empty, application/x-www-form-urlencoded
623     ///   is used.
624     /// @sa NewRequest() CHttpRequest
625     CHttpResponse Put(const CUrl&     url,
626                       CTempString     data,
627                       CTempString     content_type = CTempString(),
628                       const CTimeout& timeout = CTimeout(CTimeout::eDefault),
629                       THttpRetries    retries = null);
630 
631     /// Get all stored cookies.
Cookies(void) const632     const CHttpCookies& Cookies(void) const { return m_Cookies; }
633     /// Get all stored cookies, non-const.
Cookies(void)634     CHttpCookies& Cookies(void) { return m_Cookies; }
635 
636     /// HTTP protocol version.
637     enum EProtocol {
638         eHTTP_10, ///< HTTP/1.0
639         eHTTP_11, ///< HTTP/1.1
640         eHTTP_2,  ///< HTTP/2
641     };
642 
643     /// Get protocol version.
GetProtocol(void) const644     EProtocol GetProtocol(void) const { return m_Protocol; }
SetProtocol(EProtocol protocol)645     void SetProtocol(EProtocol protocol) { m_Protocol = protocol; }
646 
647     /// Get flags passed to CConn_HttpStream.
648     /// @sa SetHttpFlags
GetHttpFlags(void) const649     THTTP_Flags GetHttpFlags(void) const { return m_HttpFlags; }
650     /// Set flags passed to CConn_HttpStream. When sending request,
651     /// fHTTP_AdjustOnRedirect is always added to the flags.
652     /// @sa GetHttpFlags
SetHttpFlags(THTTP_Flags flags)653     void SetHttpFlags(THTTP_Flags flags) { m_HttpFlags = flags; }
654 
655     CHttpSession_Base(void);
~CHttpSession_Base(void)656     virtual ~CHttpSession_Base(void) {}
657 
658 private:
659     friend class CHttpRequest;
660     friend class CHttpResponse;
661 
662     virtual void x_StartRequest(EProtocol protocol, CHttpRequest& req, bool use_form_data) = 0;
663     virtual bool x_Downgrade(CHttpResponse& resp, EProtocol& protocol) const = 0;
664 
665     // Save cookies returned by a response.
666     void x_SetCookies(const CHttpHeaders::THeaderValues& cookies,
667                       const CUrl*                        url);
668     // Get a single 'Cookie' header line for the url.
669     string x_GetCookies(const CUrl& url) const;
670 
671     EProtocol    m_Protocol;
672     THTTP_Flags  m_HttpFlags;
673     CHttpCookies m_Cookies;
674 };
675 
676 
677 template <class TImpl>
678 class CHttpSessionTmpl : public CHttpSession_Base
679 {
x_StartRequest(EProtocol protocol,CHttpRequest & req,bool use_form_data)680     void x_StartRequest(EProtocol protocol, CHttpRequest& req, bool use_form_data) override
681     {
682         TImpl::StartRequest(protocol, req, use_form_data);
683     }
684 
x_Downgrade(CHttpResponse & resp,EProtocol & protocol) const685     bool x_Downgrade(CHttpResponse& resp, EProtocol& protocol) const override
686     {
687         return TImpl::Downgrade(resp, protocol);
688     }
689 };
690 
691 
692 class CHttpSessionImpl1x
693 {
694     friend class CHttpSessionTmpl<CHttpSessionImpl1x>;
695 
StartRequest(CHttpSession_Base::EProtocol _DEBUG_ARG (protocol),CHttpRequest & req,bool use_form_data)696     static void StartRequest(CHttpSession_Base::EProtocol _DEBUG_ARG(protocol), CHttpRequest& req, bool use_form_data)
697     {
698         _ASSERT(protocol <= CHttpSession_Base::eHTTP_11);
699         req.x_InitConnection(use_form_data);
700     }
701 
Downgrade(CHttpResponse &,CHttpSession_Base::EProtocol &)702     static bool Downgrade(CHttpResponse&, CHttpSession_Base::EProtocol&)
703     {
704         return false;
705     }
706 };
707 
708 
709 /// @sa CHttpSession_Base
710 using CHttpSession = CHttpSessionTmpl<CHttpSessionImpl1x>;
711 
712 
713 /////////////////////////////////////////////////////////////////////////////
714 ///
715 /// CHttpSessionException --
716 ///
717 ///   Exceptions used by CHttpSession, CHttpRequest and CHttpResponse
718 ///   classes.
719 
720 class NCBI_XCONNECT_EXPORT CHttpSessionException : public CException
721 {
722 public:
723     enum EErrCode {
724         eBadRequest,      ///< Error initializing or sending a request.
725         eBadContentType,  ///< Content-type conflicts with the data.
726         eBadFormDataName, ///< Empty or bad name in form data.
727         eBadFormData,     ///< Bad form data (e.g. unreadable file).
728         eBadStream,       ///< Wrong stream used to read content or error.
729         eOther
730     };
731 
732     virtual const char* GetErrCodeString(void) const override;
733 
734     NCBI_EXCEPTION_DEFAULT(CHttpSessionException, CException);
735 };
736 
737 
738 END_NCBI_SCOPE
739 
740 
741 /* @} */
742 
743 #endif  /* CONNECT___NCBI_HTTP_SESSION__HPP */
744