/*------------------------------------------------------------------------
* (The MIT License)
*
* Copyright (c) 2008-2011 Rhomobile, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* http://rhomobile.com
*------------------------------------------------------------------------*/
using System;
using rho.common;
using rho;
using System.Collections.Generic;
namespace rho.net
{
public class CHttpServer : CThreadQueue
{
private static RhoLogger LOG = RhoLogger.RHO_STRIP_LOG ? new RhoEmptyLogger() :
new RhoLogger("HttpServer");
String m_root;
static CRhodesApp RHODESAPP() { return CRhodesApp.Instance; }
private CRhoRuby RhoRuby { get { return CRhoRuby.Instance; } }
class CRoute
{
public String application = "";
public String model = "";
public String id = "";
public String action = "";
public static bool isid(String s)
{
return s.Length > 2 && s[0] == '{' && s[s.Length - 1] == '}';
}
};
/*public class CResponse
{
public String m_strUrl = "";
public bool m_bRedirect = true;
public CResponse(String strUrl) { m_strUrl = strUrl; m_bRedirect = true; }
public CResponse(bool bRedirect) { m_bRedirect = bRedirect; }
};*/
class CServerCommand : IQueueCommand
{
String m_method, m_uri, m_query, m_body;
CRoute m_route;
CHttpServer m_Parent;
int m_nCommand;
public const int scDispatch = 1, scIndex = 2;
public CServerCommand(CHttpServer Parent, CRoute route,
String method, String uri, String query, String body, int nCommand)
{
m_Parent = Parent;
m_method = method;
m_uri = uri;
m_query = query;
m_body = body;
m_route = route;
m_nCommand = nCommand;
}
public void execute()
{
String strUrl = "";
if (m_nCommand == scDispatch )
strUrl = m_Parent.processDispatch(m_route, m_method, m_uri, m_query, m_body);
else if ( m_nCommand == scIndex )
strUrl = m_Parent.processIndex(m_route, m_method, m_uri, m_query, m_body);
RHODESAPP().processWebNavigate(strUrl, -1);
}
public boolean equals(IQueueCommand cmd) { return false; }
public void cancel() { }
public String toString() { return "uri :" + m_uri + "; cmd: " + (m_nCommand == scDispatch ? "dispatch" : "index"); }
};
public CHttpServer(String strFileRoot)
{
m_root = strFileRoot;
base.setLogCategory(LOG.getLogCategory());
setPollInterval(QUEUE_POLL_INTERVAL_INFINITE);
start(epNormal);
}
public override void run()
{
RHODESAPP().startApp();
base.run();
RHODESAPP().stopApp();
}
public override void processCommand(IQueueCommand pCmd)
{
(pCmd as CServerCommand).execute();
}
public boolean processBrowserRequest(Uri uri)
{
if (!uri.OriginalString.StartsWith(RHODESAPP().getHomeUrl())
&& (uri.IsAbsoluteUri || uri.OriginalString.StartsWith("res:")))
return false;
String query = "";
String url = uri.OriginalString;
url = url.StartsWith(RHODESAPP().getHomeUrl()) ? url.substring(RHODESAPP().getHomeUrl().length()) : url;
int nFrag = url.LastIndexOf('#');
if (nFrag >= 0)
url = url.Substring(0, nFrag);
int nQuery = url.IndexOf('?');
if (nQuery >= 0)
{
query = url.Substring(nQuery + 1);
url = url.Substring(0, nQuery);
}
if (url.EndsWith(".gen.html") || (isknowntype(url) && url.StartsWith(m_root)))
return false;
CRoute route = new CRoute();
if (dispatch(url, route))
{
addQueueCommand(new CServerCommand(this, route, "GET", url, query, "", CServerCommand.scDispatch));
return true;
}
String fullPath = url.StartsWith(m_root) ? url : CFilePath.join(m_root, url);
String strIndexFile = getIndex(fullPath);
if (strIndexFile.Length > 0)
{
addQueueCommand(new CServerCommand(this, route, "GET", url, query, "", CServerCommand.scIndex));
return true;
}
return false;
}
String processDispatch(CRoute route, String method, String uri, String query, String body)
{
Object rhoReq = create_request_hash(route, method, uri, query, null, body);
Object rhoResp = RhoRuby.callServe(rhoReq);
String strRedirectUrl = getRedirectUrl(rhoResp);
if (strRedirectUrl.Length > 0)
return strRedirectUrl;
String strFilePath = RHODESAPP().canonicalizeRhoPath(uri) + ".gen.html";
if (route.id.Length > 0)
strFilePath = CFilePath.join(m_root, route.application + "/" + route.model + "/" + route.action) + ".gen.html";
CRhoFile.recursiveCreateDir(strFilePath);
CRhoFile.writeDataToFile(strFilePath, getResponseBody(rhoResp));
if (method == "GET")
RHODESAPP().keepLastVisitedUrl(uri);
return strFilePath;
}
String processIndex(CRoute route, String method, String uri, String query, String body)
{
String fullPath = uri.StartsWith(m_root) ? uri : CFilePath.join(m_root, uri);
String strIndexFile = getIndex(fullPath);
if (!CRhoFile.isResourceFileExist(strIndexFile))
{
String error = "404 Not Found.
The file " + uri + " was not found.";
String strFilePath = CFilePath.join(m_root, "rhodes_error") + ".gen.html";
CRhoFile.recursiveCreateDir(strFilePath);
CRhoFile.writeStringToFile(strFilePath, error);
return strFilePath;
}
if (CFilePath.getExtension(fullPath).Length > 0)
return strIndexFile;
Object rhoReq = create_request_hash(route, method, uri, query, null, body);
Object rhoResp = RhoRuby.callServeIndex(strIndexFile, rhoReq);
String strRedirectUrl = getRedirectUrl(rhoResp);
if (strRedirectUrl.Length > 0)
return strRedirectUrl;
strIndexFile += ".gen.html";
CRhoFile.recursiveCreateDir(strIndexFile);
CRhoFile.writeDataToFile(strIndexFile, getResponseBody(rhoResp));
if (method == "GET")
RHODESAPP().keepLastVisitedUrl(uri);
return strIndexFile;
}
/*
CResponse decide(String method, String uri, String query, String body)
{
//if (process_registered(uri))
// return new CResponse(false);
CRoute route = new CRoute();
if (dispatch(uri, route))
{
Object rhoReq = create_request_hash(route, method, uri, query, null, body);
Object rhoResp = RhoRuby.callServe(rhoReq);
String strRedirectUrl = getRedirectUrl(rhoResp);
if (strRedirectUrl.Length > 0)
return new CResponse(strRedirectUrl);
String strFilePath = RHODESAPP().canonicalizeRhoPath(uri) + ".gen.html";
if ( route.id.Length > 0 )
strFilePath = CFilePath.join(m_root, route.application + "/" + route.model + "/" + route.action) + ".gen.html";
CRhoFile.recursiveCreateDir(strFilePath);
CRhoFile.writeDataToFile(strFilePath, getResponseBody(rhoResp));
if (method == "GET")
RHODESAPP().keepLastVisitedUrl(uri);
return new CResponse(strFilePath);
}
String fullPath = uri.StartsWith(m_root) ? uri : CFilePath.join(m_root, uri);
String strIndexFile = getIndex(fullPath);
if (strIndexFile.Length > 0 )
{
if (!CRhoFile.isResourceFileExist(strIndexFile))
{
String error = "404 Not Found.
The file " + uri + " was not found.";
String strFilePath = CFilePath.join(m_root,"rhodes_error") + ".gen.html";
CRhoFile.recursiveCreateDir(strFilePath);
CRhoFile.writeStringToFile(strFilePath, error);
return new CResponse(strFilePath);
}
if (CFilePath.getExtension(fullPath).Length > 0)
return new CResponse(strIndexFile);
Object rhoReq = create_request_hash(route, method, uri, query, null, body);
Object rhoResp = RhoRuby.callServeIndex(strIndexFile, rhoReq);
String strRedirectUrl = getRedirectUrl(rhoResp);
if (strRedirectUrl.Length > 0)
return new CResponse(strRedirectUrl);
strIndexFile += ".gen.html";
CRhoFile.recursiveCreateDir(strIndexFile);
CRhoFile.writeDataToFile(strIndexFile, getResponseBody(rhoResp));
if (method == "GET")
RHODESAPP().keepLastVisitedUrl(uri);
return new CResponse(strIndexFile);
}
return new CResponse(false);
}*/
public bool call_ruby_method(String uri, String body, out String strReply)
{
CRoute route = new CRoute();
strReply = String.Empty;
if (!dispatch(uri, route))
{
if (route.application.equalsIgnoreCase("system"))
{
if (route.model.equalsIgnoreCase("geolocation"))
{
//TODO: geolocation
//showGeoLocation();
return true;
}
else if (route.model.equalsIgnoreCase("loadserversources"))
{
RhoRuby.loadServerSources(body);
return true;
}
else if (route.model.equalsIgnoreCase("loadallsyncsources"))
{
RhoRuby.loadAllSyncSources();
return true;
}
else if (route.model.equalsIgnoreCase("resetDBOnSyncUserChanged"))
{
RhoRuby.resetDBOnSyncUserChanged();
return true;
}
}
return false;
}
Dictionary headers = new Dictionary();
headers.Add("Content-Type","application/x-www-form-urlencoded");
Object rhoReq = create_request_hash(route, "POST", uri, String.Empty, headers, body);
Object rhoResp = RhoRuby.callServe(rhoReq);
strReply = getResponseBodyString(rhoResp);
return true;
}
byte[] getResponseBody(Object resp)
{
Object body = RhoRuby.hashGet(resp, "request-body");
return RhoRuby.getBytesFromString(body);
}
String getResponseBodyString(Object resp)
{
Object body = RhoRuby.hashGet(resp, "request-body");
return RhoRuby.getStringFromObject(body);
}
String getRedirectUrl(Object resp)
{
int nStatus = RhoRuby.hashGetInt(resp, "status");
if ( nStatus != 302 )
return String.Empty;
return RhoRuby.hashGetString(resp, "Location");
}
bool dispatch(String uri, CRoute route)
{
if (isknowntype(uri))
return false;
// Trying to parse route
if (!parse_route(uri, route))
return false;
// Convert CamelCase to underscore_case
String controllerName = "";
for (int i = 0; i < route.model.Length; i++)
{
if (Char.IsUpper(route.model[i]) && i > 0)
controllerName += '_';
controllerName += route.model[i];
}
controllerName = controllerName.ToLower();
String model_name_controller = m_root + "/" + route.application + "/" + route.model + "/" + controllerName + "_controller.rb";
String controller = m_root + "/" + route.application + "/" + route.model + "/controller.rb";
return CRhoFile.isResourceFileExist(model_name_controller) || CRhoFile.isResourceFileExist(controller);
}
bool parse_route(String uri, CRoute route)
{
if (uri.StartsWith(RHODESAPP().getHomeUrl()))
uri = uri.Substring(RHODESAPP().getHomeUrl().Length + 1);
if (uri.StartsWith(m_root))
uri = uri.Substring(m_root.Length+1);
else if ( uri.StartsWith("/") )
uri = uri.Remove(0, 1 );
String[] arParts = uri.Split('/');
if (arParts.Length > 0 )
route.application = arParts[0];
if (arParts.Length < 2)
return false;
route.model = arParts[1];
if (arParts.Length < 3)
return true;
String aoi = arParts[2];
if (CRoute.isid(aoi))
{
route.id = aoi;
if (arParts.Length > 3)
route.action = arParts[3];
}
else
{
if (arParts.Length > 3)
route.id = arParts[3];
route.action = aoi;
}
return true;
}
bool isknowntype(String uri)
{
String[] ignored_exts = { ".css", ".js", ".html", ".htm", ".png", ".bmp", ".jpg", ".jpeg", ".gif" };
String strExt = CFilePath.getExtension(uri).ToLower();
return find_in_string_array(strExt, ignored_exts);
}
String getIndex(String path)
{
String[] index_files = { "index_erb.rb", "index.html", "index.htm", "index.php", "index.cgi" };
if (CFilePath.getExtension(path).Length == 0)
return CFilePath.join(path, index_files[0]);
String strFileName = CFilePath.getBaseName(path);
return find_in_string_array(strFileName, index_files) ? path : "";
}
public bool process_registered(String uri)
{
//TODO: process_registered
return false;
}
Object create_request_hash(CRoute route, String method, String uri, String query,
Dictionary headers, String body)
{
Object hash = RhoRuby.createHash();
if ( route != null )
{
RhoRuby.hashAdd(hash, "application", route.application);
RhoRuby.hashAdd(hash, "model", route.model);
if ( route.action.Length > 0 )
RhoRuby.hashAdd(hash, "action", route.action);
if ( route.id.Length > 0 )
RhoRuby.hashAdd(hash, "id", route.id);
}
RhoRuby.hashAdd(hash, "request-method", method);
RhoRuby.hashAdd(hash, "request-uri", uri);
RhoRuby.hashAdd(hash, "request-query", query);
Object hash_headers = RhoRuby.createHash();
if (headers != null)
{
Dictionary.Enumerator hashEnum = headers.GetEnumerator();
while (hashEnum.MoveNext())
{
RhoRuby.hashAdd(hash_headers, hashEnum.Current.Key, hashEnum.Current.Value);
}
}
RhoRuby.hashAdd(hash, "headers", hash_headers);
if ( body.Length > 0 )
RhoRuby.hashAdd(hash, "request-body", body);
return hash;
}
bool find_in_string_array(String str, String[] ar)
{
for (int i = 0; i < ar.Length; i++)
{
if (str.CompareTo(ar[i]) == 0)
return true;
}
return false;
}
}
}