#include "Skylark.h" #if 0 #define LLOG(x) DLOG(x) #define LDUMP(x) DDUMP(x) #define LDUMPC(x) DDUMPC(x) #define LDUMPM(x) DDUMPM(x) #else #define LLOG(x) #define LDUMP(x) #define LDUMPC(x) #define LDUMPM(x) #endif #define LTIMING(x) //RTIMING(x) namespace Upp { enum { DISPATCH_VARARGS = -1 }; struct DispatchNode : Moveable { // Single node in url hierarchy tree VectorMap subnode; Callback1 handler; // Associated handler, if the dispatch ends at this node int argpos; int method; // Type of method - GET or POST bool post_raw; // true for POST_RAW String id; // Handler ID enum { GET, POST }; DispatchNode() { argpos = Null; method = GET; post_raw = false; } }; static Vector& sDispatchMap() { static Vector x; return x; } static VectorMap >& sLinkMap() { static VectorMap > x; return x; } static Index& sHandlerIndex() { // map of handler functions for Redirect static Index x; // use uintptr_t because it has defined hashing return x; } void DumpDispatchMap() { Vector& DispatchMap = sDispatchMap(); for(int i = 0; i < DispatchMap.GetCount(); i++) { LLOG("-------------"); String sub; for(int j = 0; j < DispatchMap[i].subnode.GetCount(); j++) sub << DispatchMap[i].subnode.GetKey(j) << "->" << DispatchMap[i].subnode[j] << ", "; LLOG(i << " " << (bool)DispatchMap[i].handler << ": " << sub); } } String SkylarkAppendPath__(const String& path_prefix, const String& path) { String r = path_prefix; if(path_prefix.GetCount() || path.GetCount()) r << '/'; r << path; return r; } Http& DummyHttp() { static Http http; return http; } Vector *GetUrlViewLinkParts(const String& id) { int q = sLinkMap().Find(id); if(q < 0) return NULL; return &sLinkMap()[q]; } String MakeLink0(int q, const Vector& arg) { if(q < 0) throw Exc("Invalid view"); if(q < 0) return String(); StringBuffer out; MakeLink(out, sLinkMap()[q], arg); return out; } String MakeLink(const HandlerId& id, const Vector& arg) { return MakeLink0(id.handler ? sHandlerIndex().Find((uintptr_t)id.handler) : sLinkMap().Find(id.id), arg); } void RegisterView0(void (*fn)(Http&), Callback1 cb, const char *id, String path, bool primary) { SKYLARKLOG("Register Handler " << id << " -> " << path); ASSERT_(sLinkMap().Find(id) < 0, "duplicate handler id " + String(id)); Vector& linkpart = sLinkMap().GetAdd(id); ASSERT_(!fn || sHandlerIndex().Find((uintptr_t)fn) < 0, "duplicate view function registration " + String(id)); sHandlerIndex().Add((uintptr_t)fn); Vector& DispatchMap = sDispatchMap(); int method = DispatchNode::GET; bool post_raw = false; int q = path.Find(':'); if(q >= 0) { if(path.Mid(q + 1) == "POST") method = DispatchNode::POST; if(path.Mid(q + 1) == "POST_RAW") { method = DispatchNode::POST; post_raw = true; } path = path.Mid(0, q); } Vector h = Split(path, '/'); if(DispatchMap.GetCount() == 0) DispatchMap.Add(); q = 0; int linkargpos = 0; for(int i = 0; i < h.GetCount(); i++) { String s = h[i]; LLOG(" Node " << h[i]); DispatchNode& n = DispatchMap[q]; if(*s == '*') { int argpos = Null; if(IsDigit(s[1])) linkargpos = argpos = minmax(atoi(~s + 1), 0, 30); else if(s[1] == '*') argpos = DISPATCH_VARARGS; q = DispatchMap.GetCount(); LLOG(" Adding arg " << argpos << ": " << q); n.subnode.Add(Null, q); DispatchMap.Add(); DispatchMap[q].argpos = argpos; if(primary) linkpart.Add(String(linkargpos++, 1)); } else { if(primary) linkpart.Add(s); q = n.subnode.Get(s, -1); if(q < 0) { q = DispatchMap.GetCount(); LLOG(" Adding " << s << ": " << q); n.subnode.Add(s, q); DispatchMap.Add(); } } } ASSERT_(!DispatchMap[q].handler, "duplicate view " + String(path)); DispatchMap[q].handler = fn ? callback(fn) : cb; DispatchMap[q].method = method; DispatchMap[q].id = id; DispatchMap[q].post_raw = post_raw; // DumpDispatchMap(); } struct HandlerData { // temporary storage of handlers until FinalizeViews call void (*fn)(Http&); Callback1 cb; String id; String path; HandlerData() { fn = NULL; } }; static Array& sHandlerData() { static Array x; return x; } void RegisterHandler(void (*fn)(Http&), const char *id, const char *path) { Array& v = sHandlerData(); HandlerData& w = v.Add(); w.fn = fn; w.id = id; w.path = path; } void RegisterHandler(Callback1 cb, const char *id, const char *path) { Array& v = sHandlerData(); HandlerData& w = v.Add(); w.cb = cb; w.id = id; w.path = path; } void SkylarkApp::FinalizeViews() {// the reason for HandlerData/FinalizeViews is to have a chance to ReplaceVars Array& w = sHandlerData(); for(int i = 0; i < w.GetCount(); i++) { const HandlerData& v = w[i]; String p = v.path; if(*p == '/') p = p.Mid(1); else p = root + '/' + p; Vector h = Split(ReplaceVars(p, view_var, '$'), ';'); for(int i = 0; i < h.GetCount(); i++) RegisterView0(v.fn, v.cb, v.id, h[i], i == 0); } w.Clear(); } struct BestDispatch { // Information about the best dispatch node for given path Callback1 handler; int matched_parts; // number of matched path elements int matched_params; // number of '*' arguments extracted Vector& arg; // arguments extracted String id; // id of handler bool post_raw; // handler is POST_RAW type BestDispatch(Vector& arg) : arg(arg) { matched_parts = -1; matched_params = 0; post_raw = false; } }; void GetBestDispatch(int method, const Vector& part, int ii, const DispatchNode& n, Vector& arg, BestDispatch& bd, int matched_parts, int matched_params) {// find the best DispatchNode for given path, best is the one with most path elements matched, if equal, more '*' used (not '**') Vector& DispatchMap = sDispatchMap(); if(ii >= part.GetCount()) { // we have reached the end of path if(n.handler && n.method == method && (matched_parts > bd.matched_parts || matched_parts == bd.matched_parts && matched_params > bd.matched_params)) { // node has handler LLOG("Matched " << n.id << " parts " << matched_parts << " params " << matched_params); bd.arg <<= arg; bd.handler = n.handler; bd.matched_parts = matched_parts; bd.id = n.id; bd.post_raw = n.post_raw; } if(!bd.handler) { // node does not have handler, try '**' subnode int q = n.subnode.Find(String()); while(q >= 0) { const DispatchNode& an = DispatchMap[n.subnode[q]]; if(an.argpos == DISPATCH_VARARGS && an.handler && an.method == method) { bd.handler = an.handler; bd.id = n.id; bd.arg.Clear(); break; } q = n.subnode.FindNext(q); } } return; } int qq = n.subnode.Get(part[ii], -1); if(qq >= 0) // path element matched, try subnodes first GetBestDispatch(method, part, ii + 1, DispatchMap[qq], arg, bd, matched_parts + 1, matched_params); int q = n.subnode.Find(String()); while(q >= 0) { int qq = n.subnode[q]; int ac = arg.GetCount(); const DispatchNode& an = DispatchMap[qq]; int apos = an.argpos; LLOG(" *" << qq << " apos: " << apos); if(apos == DISPATCH_VARARGS) { if(an.handler && an.method == method && (matched_parts > bd.matched_parts || matched_parts == bd.matched_parts && matched_params > bd.matched_params)) { LLOG("Matched VARARGS " << an.id << " parts " << matched_parts << " params " << matched_params); bd.arg <<= arg; bd.arg.Append(part, ii, part.GetCount() - ii); bd.handler = an.handler; bd.matched_parts = matched_parts; bd.id = an.id; } } else { String pv; if(IsNull(apos)) arg.Add(part[ii]); else { String& at = arg.At(apos); pv = at; at = part[ii]; } GetBestDispatch(method, part, ii + 1, an, arg, bd, matched_parts, matched_params + 1); if(!IsNull(apos)) arg[apos] = pv; } arg.SetCount(ac); q = n.subnode.FindNext(q); } } void Http::Dispatch(TcpSocket& socket) { const Vector& DispatchMap = sDispatchMap(); if(hdr.Read(socket)) { int len = GetLength(); content = socket.GetAll(len); LLOG("--------------------------------------------"); SKYLARKLOG(hdr.GetMethod() << " " << hdr.GetURI()); LDUMP(content); String r; var.Clear(); arg.Clear(); LTIMING("Request processing"); request_content_type = GetHeader("content-type"); String rc = ToLower(request_content_type); bool post = hdr.GetMethod() == "POST"; if(post) if(rc.StartsWith("application/x-www-form-urlencoded")) ParseRequest(content); else if(rc.StartsWith("multipart/")) ReadMultiPart(content); String uri = hdr.GetURI(); int q = uri.Find('?'); if(q >= 0) { if(!post) ParseRequest(~uri + q + 1); uri.Trim(q); } for(int i = hdr.fields.Find("cookie"); i >= 0; i = hdr.fields.FindNext(i)) { const String& h = hdr.fields[i]; int q = 0; for(;;) { int qq = h.Find('=', q); if(qq < 0) break; String id = ToLower(TrimBoth(h.Mid(q, qq - q))); qq++; q = h.Find(';', qq); if(q < 0) { var.Add('@' + id, UrlDecode(h.Mid(qq))); break; } var.Add('@' + id, UrlDecode(h.Mid(qq, q - qq))); q++; } } var.GetAdd("static") = app.static_dir; var.GetAdd(".__identity__"); // To make StdLib.icpp GetIndentity work without changing preset stack positions LDUMPM(var); Vector part = Split(uri, '/'); for(int i = 0; i < part.GetCount(); i++) part[i] = UrlDecode(part[i]); LDUMPC(part); Vector a; BestDispatch bd(arg); if(DispatchMap.GetCount()) GetBestDispatch(post ? DispatchNode::POST : DispatchNode::GET, part, 0, DispatchMap[0], a, bd, 0, 0); LDUMPC(arg); response.Clear(); if(bd.handler) { try { if(SQL.IsOpen()) SQL.Begin(); LoadSession(); session_dirty = false; if(post && !bd.post_raw) { String id = Nvl((*this)["__post_identity__"], (*this)["__js_identity__"]); if(id != (*this)[".__identity__"]) throw AuthExc("identity error"); } lang = Nvl(Int(".__lang__"), LNG_ENGLISH); Upp::SetLanguage(lang); var.GetAdd(".__lang__") = lang; var.GetAdd(".language") = ToLower(LNGAsText(lang)); handlerid = bd.id; LDUMP(handlerid); bd.handler(*this); if(session_dirty) SaveSession(); if(SQL.IsOpen()) SQL.Commit(); } catch(SqlExc e) { if(SQL.IsOpen()) SQL.Rollback(); response << "Internal server error
" << "SQL ERROR: " << e; code = 500; code_text = "Internal server error"; app.SqlError(*this); } catch(AuthExc e) { if(SQL.IsOpen()) SQL.Rollback(); response << e; code = 403; code_text = "Unauthorized"; app.Unauthorized(*this); } catch(BadRequestExc e) { if(SQL.IsOpen()) SQL.Rollback(); response << "Bad request"; code = 400; code_text = "Bad request"; app.Unauthorized(*this); } catch(Exc e) { if(SQL.IsOpen()) SQL.Rollback(); response << "Internal server error
" << e; code = 500; code_text = "Internal server error"; app.InternalError(*this); } } else { response << "Page not found"; code = 404; code_text = "Not found"; app.NotFound(*this); } r.Clear(); SKYLARKLOG("=== Response: " << code << ' ' << code_text); if(redirect.GetCount()) { SKYLARKLOG("Redirect to: " << redirect); r << "HTTP/1.1 " << code << " Found\r\n"; r << "Location: " << redirect << "\r\n"; for(int i = 0; i < cookies.GetCount(); i++) r << cookies[i]; } else { r << "HTTP/1.1 " << code << ' ' << code_text << "\r\n" "Date: " << WwwFormat(GetUtcTime()) << "\r\n" "Content-Length: " << response.GetCount() << "\r\n" "Content-Type: " << content_type << "\r\n"; for(int i = 0; i < headers.GetCount(); i++) r << headers.GetKey(i) << ": " << headers[i] << "\r\n"; for(int i = 0; i < cookies.GetCount(); i++) r << cookies[i]; r << "\r\n"; } socket.PutAll(r); socket.PutAll(response); } } };