A Http lite interface .. allow asynce responces.

This commit is contained in:
Roger Hughston 2007-08-27 23:29:27 +00:00
parent baea170229
commit 795c46b32d
16 changed files with 1777 additions and 0 deletions

View File

@ -0,0 +1,2 @@
#define LOGINFO printf
#define LOGWARNING printf

View File

@ -0,0 +1,65 @@
#ifndef __BASEINCOMINGSET_H__
#define __BASEINCOMINGSET_H__
#include <list>
#include "socket_base.h"
enum CloseState
{
ConnectionDoNotClose,
ConnectionDoClose
};
// RHH
////////////////////////////////////////////////////////////////////
// Template :BaseIncomingSet
//
// Description : A base structre for a listening socket and a
// set of connection that have been received with there read functions..
//
// Think of this like a web server with 1 listening socket and 0-n open reacting conections..
//
// The general operation if get connection..
// do you have a message
// process message
// go back to do you have a message or close connection
//
//
////////////////////////////////////////////////////////////////////
template < class _INCLASS1,class _IN_LISTEN, class MESSAGE_READER_BUF, class MESSAGE_READER_UPPASS> class BaseIncomingSet : public std::list<_INCLASS1 *>
{
_IN_LISTEN _Listener;
inline void AddFromListener(void);
inline int PumpReader(Time_Clock &currentTime);
inline void AddAConection(_INCLASS1 * newt);
public:
// typedef typename BaseIncomingSet<_INCLASS1, _IN_LISTEN, MESSAGE_READER_BUF, MESSAGE_READER_UPPASS>::LinkNode LinkNode;
// typedef SentDblLinkListNode_Gm SentDblLinkListNode_Gm;
inline BaseIncomingSet(void);
inline BaseIncomingSet(BaseIncomingSet &in);
virtual ~BaseIncomingSet();
inline _IN_LISTEN & GetListener(void);
inline bool init(Socket_Address &WhereFrom);
inline void PumpAll(Time_Clock &currentTime);
virtual CloseState ProcessNewConnection(SOCKET socket);
inline void AddToFDSet(Socket_fdset &set);
// inline LinkNode * GetRoot(void) { return &this->sentenal; };
BaseIncomingSet &operator=( BaseIncomingSet &inval);
void Reset();
};
#include "baseincomingset.i"
#endif //__BASEINCOMINGSET_H__

View File

@ -0,0 +1,202 @@
////////////////////////////////////////////////////////////////////
// Function name : BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::AddFromListener
// Description : Read incoming connections off the listener
//
// Return type : inline void
// Argument : void
////////////////////////////////////////////////////////////////////
template <class _INCLASS1,class _IN_LISTEN,typename MESSAGE_READER_BUF, typename MESSAGE_READER_UPPASS>
inline void BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::AddFromListener(void)
{
Socket_Address Addr1;
SOCKET newsck;
while(_Listener.GetIncomingConnection(newsck,Addr1) == true)
{
CloseState cl= ProcessNewConnection(newsck);
if(cl == ConnectionDoNotClose)
{
_INCLASS1 * newt = new _INCLASS1(newsck,Addr1);
AddAConection(newt);
}
else
DO_CLOSE(newsck);
}
}
////////////////////////////////////////////////////////////////////
// Function name : BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::PumpReader
// Description : Tries to read a record off the incoming socket
//
// Return type : inline void
// Argument : Time_Clock currentTime
////////////////////////////////////////////////////////////////////
template <class _INCLASS1,class _IN_LISTEN,typename MESSAGE_READER_BUF, typename MESSAGE_READER_UPPASS>
inline int BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::PumpReader(Time_Clock &currentTime)
{
MESSAGE_READER_BUF message;
iterator lpNext, lp;
for (lpNext = lp = begin(); lp != end() ; lp = lpNext)
{
lpNext++;
int ans = (*lp)->ReadIt(message, sizeof(message),currentTime);
if(ans < 0)
{
delete *lp;
erase(lp);
}
if(ans > 0)
{
CloseState cs = (*lp)->ProcessMessage(message,currentTime);
if( cs == ConnectionDoClose)
{
delete *lp;
erase(lp);
}
}
}
return 0;
}
////////////////////////////////////////////////////////////////////
// Function name : BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::AddAConection
// Description : Adds a member to the base container
//
// Return type : inline void
// Argument : _INCLASS1 * newt
////////////////////////////////////////////////////////////////////
template <class _INCLASS1,class _IN_LISTEN,typename MESSAGE_READER_BUF, typename MESSAGE_READER_UPPASS>
inline void BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::AddAConection(_INCLASS1 * newt)
{
push_back(newt);
}
////////////////////////////////////////////////////////////////////
// Function name : BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::BaseIncomingSet
// Description : core constructor
//
// Return type : inline
// Argument : void
////////////////////////////////////////////////////////////////////
template <class _INCLASS1,class _IN_LISTEN,typename MESSAGE_READER_BUF, typename MESSAGE_READER_UPPASS>
inline BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::BaseIncomingSet(void)
{
}
template <class _INCLASS1,class _IN_LISTEN,typename MESSAGE_READER_BUF, typename MESSAGE_READER_UPPASS>
BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::BaseIncomingSet(BaseIncomingSet &in)
{
}
////////////////////////////////////////////////////////////////////
// Function name : BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::~BaseIncomingSet
// Description : The Destructot .. will delete all members.. ??
//
// Return type :
////////////////////////////////////////////////////////////////////
template <class _INCLASS1,class _IN_LISTEN,typename MESSAGE_READER_BUF, typename MESSAGE_READER_UPPASS>
BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::~BaseIncomingSet()
{
for(iterator ii = begin(); ii != end(); ii++)
delete *ii;
}
////////////////////////////////////////////////////////////////////
// Function name : & BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::GetListener
// Description : Retyurns a pointer to the listener in this class
//
// Return type : inline _IN_LISTEN
// Argument : void
////////////////////////////////////////////////////////////////////
template <class _INCLASS1,class _IN_LISTEN,typename MESSAGE_READER_BUF, typename MESSAGE_READER_UPPASS>
inline _IN_LISTEN & BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::GetListener(void)
{
return this->Listener;
};
////////////////////////////////////////////////////////////////////
// Function name : BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::init
// Description : the second part of the 2 phase power up.. Opends the listener
//
// Return type : inline bool
// Argument : Socket_Address &WhereFrom
////////////////////////////////////////////////////////////////////
template <class _INCLASS1,class _IN_LISTEN,typename MESSAGE_READER_BUF, typename MESSAGE_READER_UPPASS>
inline bool BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::init(Socket_Address &WhereFrom)
{
if(_Listener.OpenForListen(WhereFrom,true) != true)
return false;
_Listener.SetNonBlocking();
return true;
}
////////////////////////////////////////////////////////////////////
// Function name : BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::PumpAll
// Description : THis is the polled interface to this class
//
// Return type : inline void
// Argument : Time_Clock &currentTime
////////////////////////////////////////////////////////////////////
template <class _INCLASS1,class _IN_LISTEN,typename MESSAGE_READER_BUF, typename MESSAGE_READER_UPPASS>
inline void BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::PumpAll(Time_Clock &currentTime)
{
PumpReader(currentTime); // I MOVED ORDER TO FINE TUNE THE READ OPERATIONS
AddFromListener();
}
////////////////////////////////////////////////////////////////////
// Function name : BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::ProcessNewConnection
// Description : this is the vertual function call when a new connection is created
// manly here for loging if needed...
//
// Return type : CloseState
// Argument : SOCKET socket
////////////////////////////////////////////////////////////////////
template <class _INCLASS1,class _IN_LISTEN,typename MESSAGE_READER_BUF, typename MESSAGE_READER_UPPASS>
CloseState BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::ProcessNewConnection(SOCKET socket)
{
return ConnectionDoNotClose;
}
////////////////////////////////////////////////////////////////////
// Function name : void BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::AddToFDSet
// Description : Add LIstener and Client to the FD set for polled reading
//
// Return type : inline
// Argument : Socket_Selector &set
////////////////////////////////////////////////////////////////////
template <class _INCLASS1,class _IN_LISTEN,typename MESSAGE_READER_BUF, typename MESSAGE_READER_UPPASS>
inline void BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::AddToFDSet(Socket_fdset &set1)
{
if(_Listener.Active())
set1.setForSocket(_Listener);
iterator lp;
for (lp = begin(); lp != end(); lp = lp++)
set1.setForSocket((*lp)->val);
}
template <class _INCLASS1,class _IN_LISTEN,typename MESSAGE_READER_BUF, typename MESSAGE_READER_UPPASS>
inline BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS> &BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::operator=
(BaseIncomingSet &inval)
{
if (&inval == this) return *this;
_Listener = inval._Listener;
return *this;
}
template <class _INCLASS1,class _IN_LISTEN,typename MESSAGE_READER_BUF, typename MESSAGE_READER_UPPASS>
inline void BaseIncomingSet<_INCLASS1,_IN_LISTEN,MESSAGE_READER_BUF,MESSAGE_READER_UPPASS>::Reset()
{
_Listener.Close();
iterator lpNext, lp;
for (lpNext = lp = begin(); lp != end() ; lp = lpNext)
{
lpNext++;
(*lp)->Reset();
delete *lp;
erase(lp);
}
}

View File

@ -0,0 +1,117 @@
#ifndef __BufferedWriter_Growable_H__
#define __BufferedWriter_Growable_H__
///////////////////////////////////////////////////////
// this class is for the usage of growable output...
// it is slower than buffered writer but more robust..
// it also allows for writes to take more time to the out putt..
// ie.. Write buffering.. Not just one write..
///////////////////////////////////////////////////
class BufferedWriter_Growable : public std::string
{
int _write_offset;
public:
BufferedWriter_Growable(void);
~BufferedWriter_Growable(void);
int AmountBuffered(void);
void AppendData(const char * buf, int len);
void Reset() { clear(); _write_offset = 0; };
const char * GetMessageHead(void);
int Flush(Socket_TCP &sck) ; // this is the ugly part
};
//////////////////////////////////////////////////////////////
// Function name : BufferedWriter_Growable::BufferedWriter_Growable
// Description :
// Return type : inline
// Argument : void
//////////////////////////////////////////////////////////////
inline BufferedWriter_Growable::BufferedWriter_Growable(void)
{
_write_offset = 0;
};
//////////////////////////////////////////////////////////////
// Function name : ~BufferedWriter_Growable::BufferedWriter_Growable
// Description :
// Return type : inline
// Argument : void
//////////////////////////////////////////////////////////////
inline BufferedWriter_Growable::~BufferedWriter_Growable(void)
{
}
//////////////////////////////////////////////////////////////
// Function name : BufferedWriter_Growable::AmountBuffered
// Description :
// Return type : inline int
// Argument : void
//////////////////////////////////////////////////////////////
inline int BufferedWriter_Growable::AmountBuffered(void)
{
return (int) (size() - _write_offset);
}
//////////////////////////////////////////////////////////////
// Function name : BufferedWriter_Growable::AppendData
// Description :
// Return type : inline void
// Argument : const char * buf
// Argument : int len
//////////////////////////////////////////////////////////////
inline void BufferedWriter_Growable::AppendData(const char * buf, int len)
{
append(buf, len);
}
//////////////////////////////////////////////////////////////
// Function name : char * BufferedWriter_Growable::GetMessageHead
// Description :
// Return type : inline const
// Argument : void
//////////////////////////////////////////////////////////////
inline const char * BufferedWriter_Growable::GetMessageHead(void)
{
return data() + _write_offset;
}
//////////////////////////////////////////////////////////////
// Function name : BufferedWriter_Growable::Flush
// Description :
// Return type : inline int
// Argument : SocketTCP_Gm &sck
//////////////////////////////////////////////////////////////
inline int BufferedWriter_Growable::Flush(Socket_TCP &sck) // this is the ugly part
{
int answer = 0;
int Writesize = AmountBuffered();
if(Writesize > 0)
{
const char * out1 = GetMessageHead();
int Writen = sck.SendData(out1,Writesize);
if(Writen > 0)
{
_write_offset += Writen;
answer = 1;
}
else if(Writen < 0)
{
if(GETERROR() != LOCAL_BLOCKING_ERROR)
answer = -1;
}
}
return answer;
}
#endif //__BufferedWriter_Growable_H__

View File

@ -0,0 +1,96 @@
#ifndef __WEBBUFFEREDREADER_GM_H__
#define __WEBBUFFEREDREADER_GM_H__
// RHH
#include <string>
#include "strtargetbuffer.h"
#include "ringbuffer_slide.h"
#include "application_log.h"
class Http_BufferedReader : protected RingBuffer_Slide
{
inline bool GetTermedString(char * outdata, size_t maxlen,char termchar1, char termchar2);
inline bool GetDoubleTermedString(char * outdata, int maxlen,char termchar1, char termchar2);
inline bool GetTermedStringInPLace(char ** outdata,char termchars);
inline bool GetTermedString(char * outdata, int maxlen,char * termchars);
inline bool GetSizeString(StrTargetBuffer & outdata);
public:
inline Http_BufferedReader(int in_size = 8192) ;
//
// The Read Message Interface
//
inline void ReSet(void);
inline int PumpCRRead(char * data, int maxdata, Socket_TCP &sck);
inline int PumpHTTPHeaderRead(char * data, int maxdata, Socket_TCP &sck);
inline int PumpSizeRead(StrTargetBuffer & outdata,Socket_TCP &sck);
inline int PumpEofRead(StrTargetBuffer & outdata,Socket_TCP &sck);
//inline int PumpMessageReader(CoreMessage &inmsg, Socket_TCP &sck);
template < class SOCK_TYPE>
inline int ReadPump(SOCK_TYPE &sck)
{
int answer = 0;
size_t readsize = BufferAvailabe();
if(readsize < 1)
{
FullCompress();
readsize = BufferAvailabe();
}
if(readsize > 0)
{
char * ff = GetBufferOpen();
int gotbytes = sck.RecvData(ff,(int)readsize);
if(gotbytes < 0) // some error
{
int er = GETERROR();
// if(err != LOCAL_BLOCKING_ERROR )
if(!sck.ErrorIs_WouldBlocking(gotbytes) )
{
answer = -3;
LOGINFO("Http_BufferedReader::ReadPump->Socket Level Read Error %d %d %d %s",er,gotbytes,errno,sck.GetPeerName().get_ip_port().c_str());
}
else
{
answer = 0; // try again nothing to read
}
}
else if(gotbytes > 0) // ok got some lets process it
{
_EndPos += gotbytes;
answer = 1;
}
else // 0 mean other end disconect arggggg
{
answer = -1;
LOGWARNING("Http_BufferedReader::ReadPump->Other End Closed Normal [%s]",sck.GetPeerName().get_ip_port().c_str());
}
}
else
{
std::string addstr = sck.GetPeerName().get_ip_port();
LOGWARNING("Http_BufferedReader::ReadPump->** Very Important** No Internal buffer left for read[%s] BufferSIze=[%d][%d]",
addstr.c_str(),
AmountBuffered(),
BufferAvailabe()
);
answer = -2;
}
return answer;
}
};
#include "http_bufferedreader.i"
#endif //__BUFFEREDREADER_GM_H__

View File

@ -0,0 +1,296 @@
////////////////////////////////////////////////////////////////////
// Function name : Http_BufferedReader::GetTermedString
// Description : a function that will peal a terminated string from the buffer
//
// Return type : inline bool
// Argument : char * outdata
// Argument : int maxlen
// Argument : char termchar1
// Argument : char termchar2
////////////////////////////////////////////////////////////////////
inline bool Http_BufferedReader::GetTermedString(char * outdata, size_t maxlen,char termchar1, char termchar2)
{
bool answer = false;
size_t DataAvail = FastAmountBeffered();
size_t MaxRead = maxlen;
if(MaxRead > DataAvail)
MaxRead = DataAvail;
char * wrk = FastGetMessageHead();
for(size_t x=0; x< MaxRead; x++)
{
if(wrk[x] == termchar1 || wrk[x] == termchar2)
{
memcpy(outdata,wrk,x);
outdata[x] = '\0';
_StartPos += x+1;
Compress();
answer = true;
break;
}
}
return answer;
}
////////////////////////////////////////////////////////////////////
// Function name : Http_BufferedReader::GetDoubleTermedString
// Description : a function that will peal a terminated string from the buffer
//
// This is the interface for a web request....
//
// Return type : inline bool
// Argument : char * outdata
// Argument : int maxlen
// Argument : char termchar1
// Argument : char termchar2
////////////////////////////////////////////////////////////////////
inline bool Http_BufferedReader::GetDoubleTermedString(char * outdata, int maxlen,char termchar1, char termchar2)
{
bool answer = false;
size_t DataAvail = FastAmountBeffered();
size_t MaxRead = maxlen;
if(MaxRead > DataAvail)
MaxRead = DataAvail;
char * wrk = FastGetMessageHead();
for(size_t x=1; x< MaxRead; x++)
{
if(
(wrk[x] == termchar1 && wrk[x-1] == termchar1) ||
(wrk[x] == termchar2 && wrk[x-1] == termchar2) ||
( x >= 3 && wrk[x] == termchar1 && wrk[x-2] == termchar1 && wrk[x-1] == termchar2 && wrk[x-3] == termchar2 ) ||
( x >= 3 && wrk[x] == termchar2 && wrk[x-2] == termchar2 && wrk[x-1] == termchar1 && wrk[x-3] == termchar1 )
)
{
memcpy(outdata,wrk,x);
outdata[x] = '\0';
_StartPos += x+1;
Compress();
answer = true;
break;
}
}
return answer;
}
////////////////////////////////////////////////////////////////////
// Function name : Http_BufferedReader::GetTermedStringInPLace
// Description : Will peal a string inplace for reading
//
// Return type : inline bool
// Argument : char ** outdata
// Argument : char termchars
////////////////////////////////////////////////////////////////////
inline bool Http_BufferedReader::GetTermedStringInPLace(char ** outdata,char termchars)
{
bool answer = false;
Compress();
size_t MaxRead = FastAmountBeffered();
char * wrk = FastGetMessageHead();
for(size_t x=0; x< MaxRead; x++)
{
if(wrk[x] == termchars)
{
*outdata = wrk;
wrk[x] = '\0';
_StartPos += x+1;
answer = true;
break;
}
}
return answer;
}
////////////////////////////////////////////////////////////////////
// Function name : Http_BufferedReader::GetTermedString
// Description : do a read of a termed string not in place
//
// Return type : bool
// Argument : char * outdata
// Argument : int maxlen
// Argument : char * termchars
////////////////////////////////////////////////////////////////////
bool Http_BufferedReader::GetTermedString(char * outdata, int maxlen,char * termchars)
{
bool answer = false;
size_t DataAvail = FastAmountBeffered();
size_t MaxRead = maxlen;
if(MaxRead > DataAvail)
MaxRead = DataAvail;
int tstrsize = (int)strlen(termchars);
char * wrk = FastGetMessageHead();
for(size_t x=0; x< MaxRead; x++)
{
if(memcmp(&wrk[x],termchars,tstrsize) == 0)
{
memcpy(outdata,wrk,x);
outdata[x] = '\0';
_StartPos += x+tstrsize;
Compress();
answer = true;
break;
}
}
return answer;
}
////////////////////////////////////////////////////////////////////
// Function name : Http_BufferedReader::Http_BufferedReader
// Description : constructore .. passes size up to ring buffer
//
// Return type : inline
// Argument : int in_size
////////////////////////////////////////////////////////////////////
inline Http_BufferedReader::Http_BufferedReader(int in_size) : RingBuffer_Slide(in_size)
{
}
////////////////////////////////////////////////////////////////////
// Function name : Http_BufferedReader::ReSet
// Description : Reaset all read content.. IE zero's out buffer...
//
// If you lose framing this will not help
//
// Return type : inline void
// Argument : void
////////////////////////////////////////////////////////////////////
inline void Http_BufferedReader::ReSet(void)
{
ResetContent();
}
////////////////////////////////////////////////////////////////////
// Function name : Http_BufferedReader::PumpCRRead
// Description : a upcall function to read a CR object off buffer
//
// Return type : inline int
// Argument : char * data
// Argument : int maxdata
// Argument : Socket_TCP &sck
////////////////////////////////////////////////////////////////////
inline int Http_BufferedReader::PumpCRRead(char * data, int maxdata, Socket_TCP &sck)
{
if(GetTermedString(data,maxdata,'\r','\n') == true)
return 1;
int rp = ReadPump(sck);
if(rp == 0)
return 0;
if(rp < 1)
return -1;
if(GetTermedString(data,maxdata,'\r','\n') == true)
return 1;
return 0;
}
////////////////////////////////////////////////////////////////////
// Function name : Http_BufferedReader::PumpHTTPHeaderRead
// Description : Will read a HTTP head ,, GET ..... or responce from web server
//
// Return type : inline int
// Argument : char * data
// Argument : int maxdata
// Argument : Socket_TCP &sck
////////////////////////////////////////////////////////////////////
inline int Http_BufferedReader::PumpHTTPHeaderRead(char * data, int maxdata, Socket_TCP &sck)
{
if(GetDoubleTermedString(data,maxdata,'\r','\n') == true)
return 1;
int rp = ReadPump(sck);
if(rp == 0)
return 0;
if(rp < 1)
return -1;
if(GetDoubleTermedString(data,maxdata,'\r','\n') == true)
return 1;
return 0;
}
inline int Http_BufferedReader::PumpSizeRead(StrTargetBuffer & outdata,Socket_TCP &sck)
{
if(GetSizeString(outdata) == true)
return 1;
int rp = ReadPump(sck);
if(rp == 0)
return 0;
if(rp < 1)
return -1;
if(GetSizeString(outdata) == true)
return 1;
return 0;
}
inline int Http_BufferedReader::PumpEofRead(StrTargetBuffer & outdata,Socket_TCP &sck)
{
// do a quick read
{
size_t MaxRead = FastAmountBeffered();
if(MaxRead > 0)
{
char *ff = FastGetMessageHead();
outdata.append(ff,MaxRead);
_StartPos += MaxRead;
Compress();
}
}
// pump the reader
int rp = ReadPump(sck);
if(rp == 0)
return 0;
if(rp== -1) // eof
{
// if eof clean the mess
size_t MaxRead = FastAmountBeffered();
if(MaxRead > 0)
{
char *ff = FastGetMessageHead();
outdata.append(ff,MaxRead);
_StartPos += MaxRead;
Compress();
}
return 1;
}
if(rp < 1)
return -1;
return 0;
}
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
inline bool Http_BufferedReader::GetSizeString(StrTargetBuffer & outdata)
{
size_t DataAvail = FastAmountBeffered();
size_t MaxRead = outdata.left_to_fill();
if(MaxRead > DataAvail)
MaxRead = DataAvail;
char * wrk = FastGetMessageHead();
if(MaxRead > 0)
{
char *ff = FastGetMessageHead();
outdata.append(ff,MaxRead);
_StartPos += MaxRead;
return true;
}
return false;
};

View File

@ -0,0 +1,4 @@
#include "http_connection.cxx"
#include "parsedhttprequest.cxx"
#include "http_request.cxx"

View File

@ -0,0 +1,166 @@
#include "http_connection.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////
HttpConnection::HttpConnection(SOCKET sck,Socket_Address &inaddr) :
_Timer(Time_Span(10,0)) ,
_MyAddress(inaddr),
_state(READING_HEADER)
{
SetSocket(sck);
SetNonBlocking();
SetReuseAddress();
_writer.reserve(102400);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
HttpConnection::~HttpConnection(void)
{
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
const Socket_Address & HttpConnection::GetMyAddress(void)
{
return _MyAddress;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////
int HttpConnection::DoReadHeader(char * message, int buffersize,Time_Clock &currentTime)
{
int ans = _Reader.PumpHTTPHeaderRead(message,buffersize,*this);
if(ans != 0)
{
if(ans > 0)
_headerDetail.assign(message,buffersize);
return ans;
}
if(_Timer.Expired(currentTime) == true)
{
return -1;
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
int HttpConnection::DoReadBody(char * message1, int buffersize,Time_Clock &currentTime)
{
int ans = _Reader.PumpSizeRead(_bodyDetail,*this);
if(ans != 0)
{
return ans;
}
if(_Timer.Expired(currentTime) == true)
{
return -1;
}
// ok lets process this thing..
_state = WAITING_TO_FINALIZE;
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
int HttpConnection::ReadIt(char * message, int buffersize,Time_Clock &currentTime)
{
switch (_state)
{
case(READING_HEADER):
return DoReadHeader(message, buffersize,currentTime);
break;
case(READING_POST):
return DoReadBody(message, buffersize,currentTime);
break;
case(WAITING_TO_FINALIZE):
return TryAndFinalize();
break;
case(WRITING_DATA):
return CloseStateWriter(currentTime);
break;
default:
break;
};
return ConnectionDoClose;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
CloseState HttpConnection::ProcessMessage(char * message,Time_Clock &currentTime)
{
if(_state == READING_POST )
{
_state = WAITING_TO_FINALIZE;
return ConnectionDoClose;
}
if(_parser.ParseThis(message) != true)
{
Reset();
return ConnectionDoClose;
}
// if it is a post go into read details mode and
// wait to get the post data..
// we do not support any other methoid today
if(_parser.GetRequestType() == "POST")
{
int context_length = _parser.getContentLength();
if(context_length > 0)
{
//_DoingExtraRead = true;
_state = READING_POST;
_bodyDetail.SetDataSize(context_length);
return ConnectionDoNotClose;
}
}
_state = WAITING_TO_FINALIZE;
_parser.SetBodyText(_bodyDetail);
_Timer.ResetTime(currentTime);
if(BuildPage(_writer,_parser) != true)
return ConnectionDoClose;
if(_state == WRITING_DATA)
{
if(CloseStateWriter(currentTime) <0)
return ConnectionDoClose;
}
return ConnectionDoNotClose;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
int HttpConnection::CloseStateWriter(Time_Clock &currentTime)
{
int fans = _writer.Flush(*this); // write error
if(fans < 0)
return -1;
if(_writer.AmountBuffered() <= 0) // all done
return -1;
if(_Timer.Expired(currentTime) == true) // too long
return -1;
return 0; // keep trying
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
void HttpConnection::Reset()
{
_state = ABORTING;
Close();
_Timer.ForceToExpired();
}

View File

@ -0,0 +1,61 @@
#ifndef HttpConnection_H
#define HttpConnection_H
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
#include "parsedhttprequest.h"
#include "baseincomingset.h"
#include "bufferedwriter_growable.h"
#include "http_bufferedreader.h"
class HttpConnection : public Socket_TCP
{
protected:
Http_BufferedReader _Reader;
BufferedWriter_Growable _writer;
Socket_Address _MyAddress;
Time_Out _Timer;
enum STATE_CONNECTIONS {
READING_HEADER =1,
READING_POST =2,
WAITING_TO_FINALIZE =3,
WRITING_DATA =4,
ABORTING = 5,
};
STATE_CONNECTIONS _state;
ParsedHttpRequest _parser;
StrTargetBuffer _bodyDetail;
std::string _headerDetail;
int CloseStateWriter(Time_Clock &currentTime);
public:
virtual ~HttpConnection(void);
const Socket_Address & GetMyAddress(void);
virtual bool BuildPage( BufferedWriter_Growable &_writer, ParsedHttpRequest &parser) = 0;
HttpConnection(SOCKET sck,Socket_Address &inaddr) ;
CloseState ProcessMessage(char * message,Time_Clock &currentTime);
int DoReadHeader(char * message, int buffersize,Time_Clock &currentTime);
int DoReadBody(char * message, int buffersize,Time_Clock &currentTime);
int ReadIt(char * message, int buffersize,Time_Clock &currentTime);
void Reset();
virtual CloseState TryAndFinalize() { _state = WRITING_DATA; ;return ConnectionDoNotClose; };
std::string GetFullmessage() { return _headerDetail + _bodyDetail; };
};
#endif // HttpConnection_H

View File

@ -0,0 +1,37 @@
#include "socket_base.h"
#include "http_connection.h"
#include "baseincomingset.h"
#include "socket_base.h"
#include "http_request.h"
typedef BaseIncomingSet< Http_Request , Socket_TCP_Listen , char [10240], char *> Http_Source_BaseIncomingSet;
static std::set< Http_Request * > Global_WebRequests_pendingNotify;
static Http_Source_BaseIncomingSet Global_HttpManager;
bool Http_Request::HttpManager_Initialize( unsigned short port)
{
init_network();
Socket_Address address;
address.set_port(port);
return Global_HttpManager.init(address);
}
Http_Request * Http_Request::HttpManager_GetARequest()
{
Time_Clock Know;
Global_HttpManager.PumpAll(Know);
Http_Request * answer = NULL;
std::set< Http_Request * >::iterator ii = Global_WebRequests_pendingNotify.begin();
if(ii != Global_WebRequests_pendingNotify.end())
{
answer = *ii;
Global_WebRequests_pendingNotify.erase(ii);
}
return answer;
}

View File

@ -0,0 +1,108 @@
#ifndef Http_Request_H_
#define Http_Request_H_
class Http_Request;
extern std::set< Http_Request * > Global_WebRequests_pendingNotify;
class Http_Request : public HttpConnection
{
public:
Http_Request(SOCKET sck,Socket_Address &inaddr) : HttpConnection(sck,inaddr)
{
};
~Http_Request()
{
Global_WebRequests_pendingNotify.erase(this);
};
bool BuildPage( BufferedWriter_Growable &_writer, ParsedHttpRequest &parser)
{
Global_WebRequests_pendingNotify.insert((Http_Request *)this);
_state = WAITING_TO_FINALIZE;
_Timer.ResetAll(Time_Clock::GetCurrentTime(),Time_Span(9999999,0));
return true;
};
CloseState TryAndFinalize()
{
return ConnectionDoNotClose;
};
PUBLISHED:
std::string GetRequestType()
{
return _parser.GetRequestType();
}
std::string GetRawRequest()
{
return _parser.GetRawRequest();
}
std::string GetRequestURL()
{
return _parser.GetRequestURL();
}
std::string GetSourceAddress()
{
return _MyAddress.get_ip_port();
}
void AppendToResponce(const std::string &in)
{
_writer+=in;
}
void SendThisResponce(const std::string &in)
{
_writer+=in;
Finish();
}
void Finish()
{
_Timer.ResetAll(Time_Clock::GetCurrentTime(),Time_Span(10,0));
_state = WRITING_DATA;
};
void Abort()
{
_state = ABORTING;
};
bool HasOption(std::string in)
{
const std::string * answer = _parser.GetOption(in);
if(answer != NULL)
return true;
return false;
}
char * GetOption(std::string in)
{
const std::string * answer = _parser.GetOption(in);
if(answer != NULL)
return (char *)answer->c_str();
return "";
}
std::string GetRequestOptionString()
{
return _parser.GetRequestOptionString();
}
static bool HttpManager_Initialize( unsigned short port);
static Http_Request * HttpManager_GetARequest();
};
#endif // Http_Request_H_

View File

@ -0,0 +1,214 @@
#pragma warning(disable : 4789)
#pragma warning(disable : 4786)
#include "parsedhttprequest.h"
////////////////////////////////////////////////////////////////////
inline std::string & trimleft_inplace(std::string & s)
{
s.erase(0, s.find_first_not_of(" \t\n\r"));
return s;
}
////////////////////////////////////////////////////////////////////
inline std::string & trimright_inplace(std::string & s)
{
size_t idx = s.find_last_not_of(" \t\n\r");
if (std::string::npos == idx)
{
s.erase();
}
else
{
char c = s.at(idx);
s.erase(idx, std::string::npos);
s.append(1, c);
}
return s;
}
////////////////////////////////////////////////////////////////////
inline std::string & trim_inplace(std::string & s)
{
trimleft_inplace(s);
trimright_inplace(s);
return s;
}
inline std::string trim_tonew(const std::string &in)
{
std::string ss = in;
return trim_inplace(ss);
}
std::string ParsedHttpRequest::deCanonicalize(std::string &inval)
{
std::string work("");
unsigned int x=0;
while (x < inval.size())
{
switch(inval[x])
{
case('+'):
work+=' ';
x++;
break;
case('%'):
if(x+2 < inval.size())
{
x++;
char aa[5];
char * end;
aa[0] = inval[x++];
aa[1] = inval[x++];
aa[2] = '\0';
char c = ( char ) strtoul(aa,&end,16);
work+=c;
}
else
x+=3;
break;
default:
work+=inval[x++];
break;
}
}
return work;
}
size_t ParsedHttpRequest::PullCR(std::string &src, std::string &dst)
{
size_t offset = src.find(' ');
if(offset >= 0 )
{
dst = src.substr(0,offset);
src = src.substr(offset+1);
}
return offset;
}
void ParsedHttpRequest::clear(void)
{
_RequestType = "";
_parameters.clear();
}
const std::string * ParsedHttpRequest::GetOption(const std::string & query)
{
std::map<std::string,std::string>::iterator ii;
ii = _parameters.find(query);
if(ii == _parameters.end())
return NULL;
return &ii->second;
}
bool ParsedHttpRequest::GetOption(const std::string & query, std::string & out_value)
{
std::map<std::string,std::string>::iterator ii;
ii = _parameters.find(query);
if(ii == _parameters.end())
{
out_value = "";
return false;
}
out_value = ii->second;
return true;
}
bool ParsedHttpRequest::ParseThis(char * request)
{
_Raw_Text = request;
// printf("%s\n\n",request);
std::string work1(_Raw_Text);
for(size_t pos = work1.find_first_of("\n\r\0") ; pos != std::string::npos ; pos = work1.find_first_of("\n\r\0") )
{
std::string line1 = work1.substr(0,pos);
work1 = work1.substr(pos+1);
if(line1.size() > 2)
{
// printf(" Line[%s]\n",line1.c_str());
size_t i_pos = line1.find(':');
if(i_pos != std::string::npos && i_pos > 1)
{
std::string noune = trim_tonew(line1.substr(0,i_pos));
std::string verb = trim_tonew(line1.substr(i_pos+1));
//printf(" Noune [%s][%s]\n",noune.c_str(),verb.c_str());
_header_Lines[noune] = verb;
}
}
}
//
// Get the url for the request ..
//
std::string work(request);
std::string line1 = work.substr(0,work.find_first_of("\n\r\0"));
if(line1.size() < 4)
return false;
if(PullCR(line1,_RequestType) < 3)
return false;
if(PullCR(line1,_RequestText) < 1)
return false;
size_t loc = (int)_RequestText.find('?');
if(loc != std::string::npos)
{
_Requestoptions = _RequestText.substr(loc+1);
_RequestText = _RequestText.substr(0,loc);
}
return ProcessOptionString(_Requestoptions);
}
std::string & ParsedHttpRequest::GetRequestURL(void)
{
return _RequestText;
};
bool ParsedHttpRequest::ProcessOptionString(std::string str)
{
size_t loc;
for(loc = str.find('&'); loc != std::string::npos; loc = str.find('&'))
{
std::string valset = str.substr(0,loc);
str = str.substr(loc+1);
if(ProcessParamSet(valset) != true)
return false;
}
return ProcessParamSet(str);
};
bool ParsedHttpRequest::ProcessParamSet(std::string &str)
{
std::string val("");
size_t loc = str.find('=');
if(loc != std::string::npos)
{
val = str.substr(loc+1);
str = str.substr(0,loc);
std::string ind1 = deCanonicalize(str);
_parameters[ind1] = deCanonicalize(val);
}
return true;
}

View File

@ -0,0 +1,143 @@
#ifndef __PARSEDHTTPREQUEST_GM_H__
#define __PARSEDHTTPREQUEST_GM_H__
#include "string"
#include "map"
class ParsedHttpRequest
{
protected:
std::string _Raw_Text;
std::string _RequestType;
std::string _RequestText;
std::string _Requestoptions;
std::string _BodyText;
std::map<std::string,std::string> _parameters;
std::map<std::string,std::string> _header_Lines;
std::string deCanonicalize(std::string &inval);
size_t PullCR(std::string &src, std::string &dst);
public:
void clear(void);
const std::string GetRequestOptionString() { return _Requestoptions; };
const std::string * GetOption(const std::string & query);
bool GetOption(const std::string & query, std::string & out_value);
bool ParseThis(char * request);
std::string & GetRequestURL(void);
const std::string & GetRawRequest(void) const { return _Raw_Text; };
const std::string & GetRequestType(void) const { return _RequestType; };
bool ProcessOptionString(std::string str);
bool ProcessParamSet(std::string &str);
void SetBodyText(const std::string & text)
{
_BodyText = text;
}
const std::string & getBodyText() { return _BodyText; };
int getContentLength()
{
int answer = 0;
std::map<std::string,std::string>::iterator ii = _header_Lines.find("Content-Length");
if(ii != _header_Lines.end())
answer = atol(ii->second.c_str());
return answer;
};
};
/*
class ParsedHttpResponce
{
std::string _Raw_Text;
std::string _responce_header;
std::map<std::string,std::string> _header_Lines;
public:
std::string GetresponceCode()
{
std::string answer;
size_t pos = _responce_header.find_first_of(" ");
if(pos != std::string::npos)
answer = support::trim_tonew(_responce_header.substr(pos,100));
pos = answer.find_first_of(" \t\n\r\0");
if(pos != std::string::npos)
answer = support::trim_tonew(answer.substr(0,pos));
return answer;
}
bool ParseThis(const std::string &responce)
{
_Raw_Text = responce;
int line_number = 0;
std::string work1(_Raw_Text);
for(size_t pos = work1.find_first_of("\n\r\0") ; pos != std::string::npos ; pos = work1.find_first_of("\n\r\0") )
{
std::string line1 = work1.substr(0,pos);
work1 = work1.substr(pos+1);
if(line1.size() > 2)
{
if(line_number == 0 && line1.substr(0,4) == "HTTP")
{
// the first line...
_responce_header = line1;
// printf("[%s]\n",line1.c_str());
}
size_t i_pos = line1.find(':');
if(i_pos != std::string::npos && i_pos > 1)
{
std::string noune = support::trim_tonew(line1.substr(0,i_pos));
std::string verb = support::trim_tonew(line1.substr(i_pos+1));
_header_Lines[noune] = verb;
}
line_number++;
}
}
return !_responce_header.empty();
}
size_t PullCR(std::string &src, std::string &dst)
{
size_t offset = src.find(' ');
if(offset >= 0 )
{
dst = src.substr(0,offset);
src = src.substr(offset+1);
}
return offset;
}
int getContentLength()
{
int answer = 0;
std::map<std::string,std::string>::iterator ii = _header_Lines.find("Content-Length");
if(ii != _header_Lines.end())
answer = atol(ii->second.c_str());
return answer;
};
};
*/
#endif //__PARSEDHTTPREQUEST_GM_H__

View File

@ -0,0 +1,48 @@
#ifndef __RINGBUFFER_GM_H__
#define __RINGBUFFER_GM_H__
////////////////////////////////////////////
// RHH
////////////////////////////////////////////////////////////////////
// Class : GmRingBuffer
// Description : This is an implemention of the membuffer with ring
// buffer interface on it....
//
// Main target right know is base class for network
// stream buffering both input and output
//
// see BufferedReader_Gm
// BufferedWriter_Gm
//
////////////////////////////////////////////////////////////////////
#include "membuffer.h"
class RingBuffer_Slide : protected MemBuffer
{
protected:
size_t _StartPos;
size_t _EndPos;
inline char * GetMessageHead(void);
inline char * GetBufferOpen(void);
inline void ForceWindowSlide(void);
#define FastGetMessageHead() (_Buffer+_StartPos)
#define FastAmountBeffered() (_EndPos - _StartPos)
inline bool PutFast(const char * data, size_t len);
public:
inline size_t AmountBuffered(void);
inline size_t BufferAvailabe(void);
inline void ResetContent(void);
inline RingBuffer_Slide(size_t in_size = 4096);
inline void FullCompress(void);
inline void Compress(void);
inline bool Put(const char * data, size_t len);
inline bool Get(char * data, size_t len);
};
#include "ringbuffer_slide.i"
#endif //__RINGBUFFER_GM_H__

View File

@ -0,0 +1,187 @@
/////////////////////////////////////////////////////////////
// Function name : RingBuffer_Slide::GetMessageHead
// Description : This will get a pointer to the fist undelivered data in buffer
// Return type : char *
// Argument : void
//////////////////////////////////////////////////////////
inline char * RingBuffer_Slide::GetMessageHead(void)
{
return _Buffer+_StartPos;
}
/////////////////////////////////////////////////////////////
// Function name : RingBuffer_Slide::GetBufferOpen
// Description : This will get the first writabe section of the buffer space
// Return type :
// Argument : void
//////////////////////////////////////////////////////////
inline char * RingBuffer_Slide::GetBufferOpen(void)
{
return _Buffer+_EndPos;
}
/////////////////////////////////////////////////////////////
// Function name : RingBuffer_Slide::ForceWindowSlide
// Description : Will force a compression of data // shift left to start position
// Return type : inline void
// Argument : void
//////////////////////////////////////////////////////////
inline void RingBuffer_Slide::ForceWindowSlide(void)
{
size_t len = AmountBuffered();
if(len > 0 && _StartPos != 0) // basic flush left..
{
memmove(_Buffer,GetMessageHead(),len);
_StartPos = 0;
_EndPos = len;
}
}
/////////////////////////////////////////////////////////////
// Function name : RingBuffer_Slide::AmountBuffered
// Description : Will report the number of unread chars in buffer
// Return type : int
// Argument : void
//////////////////////////////////////////////////////////
inline size_t RingBuffer_Slide::AmountBuffered(void)
{
return _EndPos - _StartPos;
}
/////////////////////////////////////////////////////////////
// Function name : RingBuffer_Slide::BufferAvailabe
// Description : Will report amount of data that is contiguas that can be writen at
// the location returned by GetBufferOpen
// Return type : inline int
// Argument : void
//////////////////////////////////////////////////////////
inline size_t RingBuffer_Slide::BufferAvailabe(void)
{
return GetBufferSize() - _EndPos;
}
/////////////////////////////////////////////////////////////
// Function name : RingBuffer_Slide::ResetContent
// Description : Throw away all inread information
// Return type : void
// Argument : void
//////////////////////////////////////////////////////////
void RingBuffer_Slide::ResetContent(void)
{
_StartPos = 0;
_EndPos = 0;
}
/////////////////////////////////////////////////////////////
// Function name : RingBuffer_Slide::RingBuffer_Slide
// Description :
// Return type : inline
// Argument : int in_size
//////////////////////////////////////////////////////////
inline RingBuffer_Slide::RingBuffer_Slide(size_t in_size) : MemBuffer(in_size)
{
_EndPos = 0;
_StartPos = 0;
}
/////////////////////////////////////////////////////////////
// Function name : RingBuffer_Slide::FullCompress
// Description : Force a compress of the data
// Return type : inline void
// Argument : void
//////////////////////////////////////////////////////////
inline void RingBuffer_Slide::FullCompress(void)
{
if(_StartPos == _EndPos)
{
_StartPos = 0;
_EndPos = 0;
}
else
{
ForceWindowSlide();
}
}
/////////////////////////////////////////////////////////////
// Function name : RingBuffer_Slide::Compress
// Description : Try and do a intelegent compress of the data space
// the algorithem is really stupid right know.. just say if i have
// read past 1/2 my space do a compress...Im open for sugestions
//
//
// Return type : inline void
// Argument : void
//////////////////////////////////////////////////////////
inline void RingBuffer_Slide::Compress(void)
{
if(_StartPos == _EndPos)
{
_StartPos = 0;
_EndPos = 0;
}
else if(_StartPos >= GetBufferSize() / 2)
{
ForceWindowSlide();
}
}
/////////////////////////////////////////////////////////////
// Function name : RingBuffer_Slide::Put
// Description : Adds Data to a ring Buffer
// Will do a compress if needed so pointers suplied by Get Call are no longer valide
//
// Return type : inline bool
// Argument : char * data
// Argument : int len
//////////////////////////////////////////////////////////
inline bool RingBuffer_Slide::Put(const char * data, size_t len)
{
bool answer = false;
if(len > BufferAvailabe() )
Compress();
if(len <= BufferAvailabe() )
{
memcpy(GetBufferOpen(),data,len);
_EndPos += len;
answer = true;
}
return answer;
}
////////////////////////////////////////////////////////////////////
// Function name : RingBuffer_Slide::PutFast
// Description :
//
// Return type : inline bool
// Argument : const char * data
// Argument : int len
////////////////////////////////////////////////////////////////////
inline bool RingBuffer_Slide::PutFast(const char * data, size_t len)
{
// no checking be carefull
memcpy(GetBufferOpen(),data,len); // should i be using memcopy..
_EndPos += len;
return true;
}
/////////////////////////////////////////////////////////////
// Function name : RingBuffer_Slide::Get
// Description : will copy the data ..
// false indicates not enogh data to read .. sorry...
//
// Return type : inline bool
// Argument : char * data
// Argument : int len
//////////////////////////////////////////////////////////
inline bool RingBuffer_Slide::Get(char * data, size_t len)
{
bool answer = false;
if(len <= AmountBuffered() )
{
memcpy(data,GetMessageHead(),len);
_StartPos += len;
Compress();
answer = true;
}
return answer;
}

View File

@ -0,0 +1,31 @@
#ifndef StrTargetBuffer_h_
#define StrTargetBuffer_h_
#include <string>
class StrTargetBuffer : public std::string
{
size_t _target_size;
public:
StrTargetBuffer() : std::string(), _target_size(0)
{
}
size_t left_to_fill()
{
if(_target_size < size())
return 0;
return _target_size - size();
};
void SetDataSize(size_t target)
{
_target_size = target;
}
size_t GetTargetSize() { return _target_size; };
};
#endif // StrTargetBuffer_h_