PLUGIN http lang: "C++" version: "1.0" date: "2022-01-30" author: "Julien BRUGUIER" maintainer: "Julien BRUGUIER " synopsis: "Low level HTTP support plugin." example: "A simple HTTP echo server" %{ .nf #!===SVMBIN=== LOG PLUGIN "svmcom.so" PLUGIN "===PLUGINLIB===" ARGUMENT STR port PROCESS "main" CODE "main" INLINE :memory com.device/s, com.device/c, http.mesg_1_1/h, STR/t :com.open com.tcp < "0.0.0.0" @&port -> &s :label loop :com.command @&s CLIENT -> &c :com.read @&c http.mesg_1_1 -> &t :http.new REPLY 200 -> &h :http.set_payload @&h @&t LENGTH :com.write @&c @&h :goto loop END MEMORY port END .fi %} seealso: %{ .BR svm_plugin_com (7) for networking. %} includes: %{ #include #include #include #include #include #include %} code: %{ struct Outils { static std::string conversion(const SVM_String& s) { return std::string(s.string,s.size); } static std::vector decoupage(const std::string& chaine, const std::string& separateur) { std::vector v; std::string c = chaine; size_t p; while((p=c.find(separateur))!=std::string::npos) { v.push_back(c.substr(0,p)); c = c.substr(p+separateur.size()); } v.push_back(c); return v; } }; struct Http { Http() = default; virtual ~Http() {}; virtual bool requete() const { return false; }; virtual bool erreur() const { return false; }; virtual void premiere_ligne(std::ostream& os) const {}; virtual Http* clone() const { return new Http(*this); }; friend std::ostream& operator<<(std::ostream& os, const Http& message) { message.premiere_ligne(os); os << "\r\n"; for(auto e: message._entetes) { os << e.first << ": " << e.second << "\r\n"; } os << "\r\n" << message._message; return os; } std::map _entetes; std::string _message; bool _erreur; protected: Http(const std::string& message) :_message(message) {}; }; struct Http_requete : public Http { Http_requete(const std::string& methode, const std::string uri) :_methode(methode),_uri(uri) {}; virtual bool requete() const { return true; } virtual void premiere_ligne(std::ostream& os) const { os << _methode << " " << _uri << " HTTP/1.1"; }; virtual Http* clone() const { return new Http_requete(*this); }; static void verifie(const void *svm, const std::string& methode) { static std::set methodes_valides = { "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH" }; auto it = methodes_valides.find(methode); if(it==methodes_valides.end()) { std::ostringstream oss; oss << "Invalid method " << methode; ERROR_EXTERNAL(http,bad_method,oss.str().c_str()); } } std::string _methode; std::string _uri; }; struct Http_reponse : public Http { Http_reponse(const unsigned int code_retour) :_code_retour(code_retour) {}; virtual void premiere_ligne(std::ostream& os) const { os << "HTTP/1.1 " << _code_retour << " " << Http_reponse::nom_code(_code_retour); }; virtual Http* clone() const { return new Http_reponse(*this); }; static std::string nom_code(unsigned int code_retour) { switch(code_retour) { case 100: return "Continue"; break; case 101: return "Switching Protocols"; break; case 200: return "OK"; break; case 201: return "Created"; break; case 202: return "Accepted"; break; case 203: return "Non-Authoritative Information"; break; case 204: return "No Content"; break; case 205: return "Reset Content"; break; case 206: return "Partial Content"; break; case 300: return "Multiple Choices"; break; case 301: return "Moved Permanently"; break; case 302: return "Found"; break; case 303: return "See Other"; break; case 304: return "Not Modified"; break; case 305: return "Use Proxy"; break; case 306: return ""; break; case 307: return "Temporary Redirect"; break; case 400: return "Bad Request"; break; case 401: return "Unauthorized"; break; case 402: return "Payment Required"; break; case 403: return "Forbidden"; break; case 404: return "Not Found"; break; case 405: return "Method Not Allowed"; break; case 406: return "Not Acceptable"; break; case 407: return "Proxy Authentication Required"; break; case 408: return "Request Timeout"; break; case 409: return "Conflict"; break; case 410: return "Gone"; break; case 411: return "Length Required"; break; case 412: return "Precondition Failed"; break; case 413: return "Request Entity Too Large"; break; case 414: return "Request-URI Too Long"; break; case 415: return "Unsupported Media Type"; break; case 416: return "Requested Range Not Satisfiable"; break; case 417: return "Expectation Failed"; break; case 418: return "I am a teapot"; break; case 500: return "Internal Server Error"; break; case 501: return "Not Implemented"; break; case 502: return "Bad Gateway"; break; case 503: return "Service Unavailable"; break; case 504: return "Gateway Timeout"; break; case 505: return "HTTP Version Not Supported"; break; default: break; } return ""; } static void verifie(const void *svm, unsigned int code_retour) { static std::set codes_valides = { 100, 101, 200, 201, 202, 203, 204, 205, 206, 300, 301, 302, 303, 304, 305, 306, 307, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 500, 501, 502, 503, 504, 505}; auto it = codes_valides.find(code_retour); if(it==codes_valides.end()) { std::ostringstream oss; oss << "Invalid return code " << code_retour; ERROR_EXTERNAL(http,bad_returncode,oss.str().c_str()); } } unsigned int _code_retour; }; struct Http_erreur : public Http { Http_erreur(const size_t ligne, const std::string& mesg) :Http(mesg), _ligne(ligne) {}; virtual bool erreur() const { return true; }; virtual Http* clone() const { return new Http_erreur(*this); }; size_t _ligne; }; %} USE TYPE com.device help: %{ This plugin is designed to work with the official plugin com. %} DEFINE FUNCTION http.protocol_mesg_1_1 STR BLN -> INT ? %{ SVM_String s_tampon = ARGV_VALUE(0,string); SVM_Boolean s_eof = ARGV_VALUE(1,boolean); std::string tampon = Outils::conversion(s_tampon); size_t debut = std::string::npos; size_t indice = tampon.find("GET"); if(indice0) return NEW_VALUE(integer,-debut); if(s_eof) return NEW_VALUE(integer,tampon.size()); size_t t = 4; indice = tampon.find("\r\n\r\n"); if(indice==std::string::npos) { indice = tampon.find("\n\n"); t=2; } if(indice!=std::string::npos) { size_t indice_taille = tampon.substr(0,indice).find("Content-Length: "); if(indice_taille==std::string::npos) return NEW_VALUE(integer,indice+t); size_t tt = tampon.find("\n",indice_taille+16); long int taille = ::atoi(tampon.substr(indice_taille+16,tt-(indice_taille-16)).c_str()); if(taille>0) { return NEW_VALUE(integer,indice+t+taille); } } return NEW_NULL_VALUE(integer); %} help: %{ Define the http.mesg_1_1 protocol. %} TYPE http.mesg_1_1 %{ type_mesg_1_1(Http *p) :_p(p) {}; type_mesg_1_1(const type_mesg_1_1& p) :_p(p._p->clone()) { }; ~type_mesg_1_1() { delete _p; } Http *_p; %} delete default: %{} copy default: %{} constant: %{ auto lignes = Outils::decoupage(std::string(value.string,value.size),"\r\n"); if(lignes.size()==0) { ERROR_EXTERNAL(http,bad_mesg_1_1,"Message incomplete line 1"); } auto premiere_ligne = Outils::decoupage(lignes[0]," "); if(premiere_ligne.size()==0) { ERROR_EXTERNAL(http,bad_mesg_1_1,"Line 1 empty"); } type_mesg_1_1 *message = nullptr; if(premiere_ligne[0]=="HTTP/1.1") { if(premiere_ligne.size()<2) { ERROR_EXTERNAL(http,bad_mesg_1_1,"Line 1 incomplete for response"); } long int code = ::atoi(premiere_ligne[1].c_str()); Http_reponse::verifie(svm,code); message = new type_mesg_1_1(new Http_reponse(code)); } else { if(premiere_ligne.size()<3) { ERROR_EXTERNAL(http,bad_mesg_1_1,"Line 1 incomplete for query"); } Http_requete::verifie(svm,premiere_ligne[0]); message = new type_mesg_1_1(new Http_requete(premiere_ligne[0],premiere_ligne[1])); } size_t e = 1; for( ; ; ++e) { if(lignes.size()<=e) { delete message; ERROR_EXTERNAL(http,bad_mesg_1_1,"No header end"); } if(lignes[e]=="") break; auto entete = Outils::decoupage(lignes[e],": "); if(entete.size()!=2) { delete message; ERROR_EXTERNAL(http,bad_mesg_1_1,"Invalid header"); } message->_p->_entetes.insert(std::make_pair(entete[0],entete[1])); } ++e; for( ; e_p->_message += lignes[e]; if(e!=lignes.size()-1) { message->_p->_message += "\r\n"; } } return message; %} print object: %{ std::ostringstream oss; oss << (*object->_p); return ::svm_string_new(svm,oss.str().c_str(),oss.str().size()); %} help: %{ Contain an HTTP query or reply. .TP This type supports copy, constant construction and string rendering. %} INTERRUPTION http.bad_mesg_1_1 help: "Interruption raised when a wrong string is parsed to create an HTTP message." INSTRUCTION http.decode STR -> http.mesg_1_1 %{ SVM_String chaine = ARGV_VALUE(0,string); void *message = ::type_mesg_1_1_constant(svm,chaine); return NEW_PLUGIN(http,mesg_1_1,message); %} help: %{ Create an HTTP query or reply from a string. The string can be received from a com.device. .TP This instruction raises an !http.bad_mesg_1_1 when the string is ill-formed. %} INSTRUCTION http.encode http.mesg_1_1 -> STR %{ void *message = ARGV_PLUGIN(0,http,mesg_1_1); SVM_String chaine = type_mesg_1_1_print(svm,message); return NEW_VALUE(string,chaine); %} help: %{ Create a string representing the HTTP query or reply. This string can be sent over a com.device. %} INTERRUPTION http.bad_returncode help: "Interruption raised when a reply is set with an invalid return code." INSTRUCTION http.new ( 'QUERY' [ 'GET' 'HEAD' 'POST' 'PUT' 'DELETE' 'CONNECT' 'OPTIONS' 'TRACE' 'PATCH' ]:method STR:uri STR:hostname | 'REPLY' INT:return_code ) -> http.mesg_1_1 %{ auto type = ARGV_KEYWORD(0); type_mesg_1_1 *message = nullptr; if(type=="QUERY") { auto methode = ARGV_KEYWORD(1); SVM_String uri = ARGV_VALUE(2,string); SVM_String hote = ARGV_VALUE(3,string); message=new type_mesg_1_1(new Http_requete(methode,Outils::conversion(uri))); message->_p->_entetes.insert(std::make_pair("Host",Outils::conversion(hote))); } else { auto code_retour = ARGV_VALUE(1,integer); Http_reponse::verifie(svm,code_retour); message=new type_mesg_1_1(new Http_reponse(code_retour)); } return NEW_PLUGIN(http,mesg_1_1,message); %} help: %{ Create a new HTTP query or reply with mandatory parameters. .TP The instruction raises an !http.bad_returncode when an invalid return code is provided. %} INSTRUCTION http.query http.mesg_1_1 -> BLN %{ type_mesg_1_1 *message = ARGV_PLUGIN(0,http,mesg_1_1); return ::svm_value_boolean_new__raw(svm,message->_p->requete()); %} help: "Return TRUE when the HTTP message is a query." INSTRUCTION http.reply http.mesg_1_1 -> BLN %{ type_mesg_1_1 *message = ARGV_PLUGIN(0,http,mesg_1_1); return ::svm_value_boolean_new__raw(svm,not message->_p->requete()); %} help: "Return TRUE when the HTTP message is a reply." INTERRUPTION http.bad_mesgtype help: "Interruption raised when an instruction is applied on the wrong HTTP message type." INSTRUCTION http.get_method http.mesg_1_1 -> STR %{ type_mesg_1_1 *message = ARGV_PLUGIN(0,http,mesg_1_1); Http_requete *requete = dynamic_cast(message->_p); if(not requete) { ERROR_EXTERNAL(http,bad_mesgtype,"Response has no method"); } return ::svm_value_string_new__buffer(svm,requete->_methode.c_str(),requete->_methode.size()); %} help: "Return the method on HTTP queries." INSTRUCTION http.set_method MUTABLE http.mesg_1_1 STR %{ type_mesg_1_1 *message = ARGV_PLUGIN(0,http,mesg_1_1); Http_requete *requete = dynamic_cast(message->_p); SVM_String methode = ARGV_VALUE(1,string); if(not requete) { ERROR_EXTERNAL(http,bad_mesgtype,"Response has no method"); } Http_requete::verifie(svm,Outils::conversion(methode)); requete->_methode = Outils::conversion(methode); %} help: "Change the method on HTTP queries." INSTRUCTION http.get_uri http.mesg_1_1 -> STR %{ type_mesg_1_1 *message = ARGV_PLUGIN(0,http,mesg_1_1); Http_requete *requete = dynamic_cast(message->_p); if(not requete) { ERROR_EXTERNAL(http,bad_mesgtype,"Response has no URI"); } return ::svm_value_string_new__buffer(svm,requete->_uri.c_str(),requete->_uri.size()); %} help: "Return the URI on HTTP queries." INSTRUCTION http.set_uri MUTABLE http.mesg_1_1 STR %{ type_mesg_1_1 *message = ARGV_PLUGIN(0,http,mesg_1_1); Http_requete *requete = dynamic_cast(message->_p); SVM_String uri = ARGV_VALUE(1,string); if(not requete) { ERROR_EXTERNAL(http,bad_mesgtype,"Response has no URI"); } requete->_uri = Outils::conversion(uri); %} help: "Change the URI on HTTP queries." INSTRUCTION http.get_returncode http.mesg_1_1 -> INT %{ type_mesg_1_1 *message = ARGV_PLUGIN(0,http,mesg_1_1); Http_reponse *reponse = dynamic_cast(message->_p); if(not reponse) { ERROR_EXTERNAL(http,bad_mesgtype,"Request has no return code"); } return NEW_VALUE(integer,reponse->_code_retour); %} help: "Return the return code on HTTP replies." INSTRUCTION http.set_returncode MUTABLE http.mesg_1_1 INT %{ type_mesg_1_1 *message = ARGV_PLUGIN(0,http,mesg_1_1); Http_reponse *reponse = dynamic_cast(message->_p); auto code_retour = ARGV_VALUE(1,integer); if(not reponse) { ERROR_EXTERNAL(http,bad_mesgtype,"Request has no return code"); } Http_reponse::verifie(svm,code_retour); reponse->_code_retour = code_retour; %} help: "Change the return code on HTTP replies." INSTRUCTION http.get_header http.mesg_1_1 STR : key -> STR ? : value %{ type_mesg_1_1 *message = ARGV_PLUGIN(0,http,mesg_1_1); SVM_String clef = ::svm_value_string_get(svm,::svm_parameter_value_get(svm,argv[1])); auto it = message->_p->_entetes.find(Outils::conversion(clef)); if(it==message->_p->_entetes.end()) { return NEW_NULL_VALUE(string); } return ::svm_value_string_new__buffer(svm,it->second.c_str(),it->second.size()); %} help: %{ Return the value of an header on HTTP query or reply from its key. .TP Return the null string when the header key is not found. %} INSTRUCTION http.set_header MUTABLE http.mesg_1_1 STR : key STR ? : value %{ type_mesg_1_1 *message = ARGV_PLUGIN(0,http,mesg_1_1); SVM_String clef = ARGV_VALUE(1,string); if(argc==2) { auto it = message->_p->_entetes.find(Outils::conversion(clef)); if(it!=message->_p->_entetes.end()) { message->_p->_entetes.erase(it); } } else { SVM_String valeur = ARGV_VALUE(2,string); message->_p->_entetes[Outils::conversion(clef)] = Outils::conversion(valeur); } %} help: %{ Change a value of an header on HTTP query or reply. .TP When the value parameter is a null string, the header is removed. Otherwise, the header value is added or modified. %} INSTRUCTION http.get_headers http.mesg_1_1 -> PTR %{ type_mesg_1_1 *message = ARGV_PLUGIN(0,http,mesg_1_1); SVM_Memory_Zone zone_tableau_principal = ::svm_memory_zone_new(svm); ::svm_memory_zone_append_internal__raw(svm,zone_tableau_principal,SVM_Type::POINTER,message->_p->_entetes.size()); SVM_Value_Pointer tableau_principal = ::svm_memory_allocate(svm,CURRENT(kernel),zone_tableau_principal); SVM_Address adresse = ::svm_value_pointer_get_address(svm,tableau_principal); for(auto e: message->_p->_entetes) { SVM_Memory_Zone zone_tableau_secondaire = ::svm_memory_zone_new(svm); ::svm_memory_zone_append_internal__raw(svm,zone_tableau_secondaire,SVM_Type::STRING,2); SVM_Value_Pointer tableau_secondaire = ::svm_memory_allocate(svm,CURRENT(kernel),zone_tableau_secondaire); SVM_Address a = ::svm_value_pointer_get_address(svm,tableau_secondaire); ::svm_memory_write_address(svm,CURRENT(kernel),a,::svm_value_string_new__buffer(svm,e.first.c_str(),e.first.size())); ::svm_memory_write_address(svm,CURRENT(kernel),a+1,::svm_value_string_new__buffer(svm,e.second.c_str(),e.second.size())); ::svm_memory_write_address(svm,CURRENT(kernel),adresse,tableau_secondaire); ++adresse; } return tableau_principal; %} help: %{ Return a pointer on an array of arrays. The main array contains one pointer by header in the HTTP query or reply. Each header pointer contains a two strings array: - The first one is the header key, - The second one is the header value. %} INSTRUCTION http.set_headers MUTABLE http.mesg_1_1 PTR %{ type_mesg_1_1 *message = ARGV_PLUGIN(0,http,mesg_1_1); std::map entetes; SVM_Value_Pointer tableau_principal = ::svm_parameter_value_get(svm,argv[1]); SVM_Address adresse = ::svm_value_pointer_get_address(svm,tableau_principal); SVM_Size taille = ::svm_value_pointer_get_size(svm,tableau_principal); for(SVM_Address a = adresse ; a<(adresse+taille) ; ++a) { SVM_Value_Pointer tableau_secondaire = ::svm_memory_read_address_typeinternal(svm,::svm_kernel_get_current(svm),a,SVM_Type::POINTER); SVM_Address aa = ::svm_value_pointer_get_address(svm,tableau_secondaire); SVM_Value_String clef = ::svm_memory_read_address_typeinternal(svm,::svm_kernel_get_current(svm),aa,SVM_Type::STRING); SVM_Value_String valeur = ::svm_memory_read_address_typeinternal(svm,::svm_kernel_get_current(svm),aa+1,SVM_Type::STRING); entetes[Outils::conversion(::svm_value_string_get(svm,clef))] = Outils::conversion(::svm_value_string_get(svm,valeur)); } message->_p->_entetes.swap(entetes); %} help: %{ Change the headers on an HTTP query or reply. The pointer references an array of arrays like the return value of the instruction .IR :http.get_headers . %} INSTRUCTION http.get_payload http.mesg_1_1 -> STR %{ type_mesg_1_1 *message = ARGV_PLUGIN(0,http,mesg_1_1); return ::svm_value_string_new__buffer(svm,message->_p->_message.c_str(),message->_p->_message.size()); %} help: "Return the payload on an HTTP query or reply." INSTRUCTION http.set_payload MUTABLE http.mesg_1_1 STR 'LENGTH' ? %{ type_mesg_1_1 *message = ARGV_PLUGIN(0,http,mesg_1_1); SVM_String mesg = ARGV_VALUE(1,string); message->_p->_message = Outils::conversion(mesg); if(argc>2) { std::ostringstream oss; oss << mesg.size; message->_p->_entetes["Content-Length"] = oss.str(); } %} help: %{ Change the payload on an HTTP query or reply. .TP The instruction also adds/modifies the Content-Length header with the size of the payload when the keyword LENGTH is present. %}