diff --git a/.gitignore b/.gitignore index b2a3a21..81c3d01 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ -project.lock.json -bin/ -obj/ -Migrations/ -*.db +project.lock.json +DNWS260.code-workspace +Dockerfile +bin/ +obj/ +out/ +Migrations/ +*.db +client/node_modules/ +client/app/bower_components/ diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000..f7c9c77 Binary files /dev/null and b/.vs/slnx.sqlite differ diff --git a/.vs/slnx.sqlite-journal b/.vs/slnx.sqlite-journal new file mode 100644 index 0000000..e52a304 Binary files /dev/null and b/.vs/slnx.sqlite-journal differ diff --git a/ClientInfoPlugin.cs b/ClientInfoPlugin.cs index 7a01615..d73d151 100644 --- a/ClientInfoPlugin.cs +++ b/ClientInfoPlugin.cs @@ -14,25 +14,25 @@ public HTTPResponse GetResponse(HTTPRequest request) { StringBuilder sb = new StringBuilder(); - string[] client_endpoint = request.getPropertyByKey("RemoteEndPoint").Split(':'); + string[] client_endpoint = request.GetPropertyByKey("RemoteEndPoint").Split(':'); string val; sb.Append(""); sb.Append("Client Port: ").Append(client_endpoint[1]).Append("
\n"); - if ((val = request.getPropertyByKey("user-agent")) != null) + if ((val = request.GetPropertyByKey("user-agent")) != null) { sb.Append("Browser Information: ").Append(val).Append("
\n"); } - if ((val = request.getPropertyByKey("accept-language")) != null) + if ((val = request.GetPropertyByKey("accept-language")) != null) { sb.Append("Accept Language: ").Append(val).Append("
\n"); } - if ((val = request.getPropertyByKey("accept-encoding")) != null) + if ((val = request.GetPropertyByKey("accept-encoding")) != null) { sb.Append("Accept Encoding: ").Append(val).Append("
\n"); } sb.Append(""); HTTPResponse response = new HTTPResponse(200); - response.body = Encoding.UTF8.GetBytes(sb.ToString()); + response.Body = Encoding.UTF8.GetBytes(sb.ToString()); return response; } diff --git a/DNWS.csproj b/DNWS.csproj index 26242d2..aa65af3 100644 --- a/DNWS.csproj +++ b/DNWS.csproj @@ -6,11 +6,14 @@ - - + + + + + - + - \ No newline at end of file + diff --git a/DNWS260.code-workspace b/DNWS260.code-workspace new file mode 100644 index 0000000..876a149 --- /dev/null +++ b/DNWS260.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": {} +} \ No newline at end of file diff --git a/DelayPlugin.cs b/DelayPlugin.cs index 006ab1e..08a8fff 100644 --- a/DelayPlugin.cs +++ b/DelayPlugin.cs @@ -26,7 +26,7 @@ public HTTPResponse GetResponse(HTTPRequest request) delay = Convert.ToInt32(parts[1]); } catch (Exception ex) { response = new HTTPResponse(400); - response.body = Encoding.UTF8.GetBytes(ex.ToString()); + response.Body = Encoding.UTF8.GetBytes(ex.ToString()); return response; } @@ -38,7 +38,7 @@ public HTTPResponse GetResponse(HTTPRequest request) sb.Append(""); sb.Append("

Sleep for " + delay + " millisecond.

"); sb.Append(""); - response.body = Encoding.UTF8.GetBytes(sb.ToString()); + response.Body = Encoding.UTF8.GetBytes(sb.ToString()); return response; } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8008971 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM microsoft/dotnet:2.0-sdk AS build-env +WORKDIR /app + +COPY *.csproj ./ +RUN dotnet restore +COPY . ./ +RUN dotnet publish -c Release -r linux-x64 -o out +#RUN dotnet publish -c Release -o out + +FROM microsoft/dotnet:2.0-runtime-deps +WORKDIR /app +COPY --from=build-env /app/out ./ +#COPY /app/out ./ +COPY ./config.json /app/out +COPY ./index.html /app/out +ENTRYPOINT [ "/app/DNWS" ] \ No newline at end of file diff --git a/GPUPlugin.cs b/GPUPlugin.cs new file mode 100644 index 0000000..222eae4 --- /dev/null +++ b/GPUPlugin.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; + +using OpenCL.Net.Extensions; +using OpenCL.Net; +using System.Linq; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace DNWS +{ + class GPUPlugin : IPluginWithParameters + { + private Context _context; + private Device _device; + private Dictionary _parameters; + private bool _isInit = false; + + public GPUPlugin() + { + } + + private void init() + { + ErrorCode error; + + // Get platform info + Platform[] platforms = Cl.GetPlatformIDs(out error); + List devicesList = new List(); + + LogError (error, "Cl.GetPlaformIDs"); + + DeviceType deviceType = DeviceType.Default; + switch(_parameters["DeviceType"]) { + case "Gpu": + deviceType = DeviceType.Gpu; + break; + case "Cpu": + deviceType = DeviceType.Cpu; + break; + case "All": + deviceType = DeviceType.All; + break; + case "Accelerator": + deviceType = DeviceType.Accelerator; + break; + } + + // Get available devices + foreach (Platform platform in platforms) { + string platformName = Cl.GetPlatformInfo(platform, PlatformInfo.Name, out error).ToString(); + Console.WriteLine("Platform: " + platformName); + LogError (error, "Cl.GetPlatformInfo"); + foreach (Device device in Cl.GetDeviceIDs(platform, deviceType, out error)) { + LogError(error, "Cl.GetDeviceIDs"); + Console.WriteLine("Device:" + device.ToString() ); + devicesList.Add(device); + } + } + + if(devicesList.Count <= 0) { + Console.WriteLine("No devices found."); + return; + } + + _device = devicesList[0]; + if(Cl.GetDeviceInfo(_device, DeviceInfo.ImageSupport, out error).CastTo() == OpenCL.Net.Bool.False) + { + Console.WriteLine("No image support."); + return; + } + _context = Cl.CreateContext(null, 1, new[] {_device}, ContextNotify, IntPtr.Zero, out error); + LogError(error, "Cl.CreateContext"); + + + } + + private void ContextNotify(string errInfo, byte[] data, IntPtr cb, IntPtr userData) { + Console.WriteLine("OpenCL Notification: " + errInfo); + } + + public void PreProcessing(HTTPRequest request) + { + throw new NotImplementedException(); + } + + private void LogError(ErrorCode err, string name) + { + if (err != ErrorCode.Success) { + Console.WriteLine("ERROR: " + name + " (" + err.ToString() + ")"); + } + } + + private StringBuilder GenUploadForm() + { + StringBuilder sb = new StringBuilder(); + sb.Append("
"); + sb.Append("Image URL:"); + sb.Append(""); + sb.Append(""); + sb.Append("
"); + return sb; + } + + public HTTPResponse GetResponse(HTTPRequest request) + { + HTTPResponse response = new HTTPResponse(200); + StringBuilder sb = new StringBuilder(); + ErrorCode error; + + if(!_isInit) { + init(); + _isInit = true; + } + + if (request.Method == HTTPRequest.METHOD_GET) { + sb.Append(""); + sb.Append(GenUploadForm()); + sb.Append(""); + response.Body = Encoding.UTF8.GetBytes(sb.ToString()); + return response; + } else if (request.Method == HTTPRequest.METHOD_POST) { + sb.Append(request.Body); + response.Body = Encoding.UTF8.GetBytes(sb.ToString()); + string programPath = System.Environment.CurrentDirectory + "/Kernel.cl"; + if(!System.IO.File.Exists(programPath)) { + Console.WriteLine("Program doesn't exist at path " + programPath); + return new HTTPResponse(404); + } + + sb.Append(""); + string programSource = System.IO.File.ReadAllText(programPath); + using (OpenCL.Net.Program program = Cl.CreateProgramWithSource(_context, 1, new[] {programSource}, null, out error)) { + LogError(error, "Cl.CreateProgramWithSource"); + error = Cl.BuildProgram(program, 1, new[] {_device}, string.Empty, null, IntPtr.Zero); + LogError(error, "Cl.BuildProgram"); + if (Cl.GetProgramBuildInfo (program, _device, ProgramBuildInfo.Status, out error).CastTo() + != BuildStatus.Success) { + LogError(error, "Cl.GetProgramBuildInfo"); + Console.WriteLine("Cl.GetProgramBuildInfo != Success"); + Console.WriteLine(Cl.GetProgramBuildInfo(program, _device, ProgramBuildInfo.Log, out error)); + return new HTTPResponse(404); + } + Kernel kernel = Cl.CreateKernel(program, "answer", out error); + LogError(error, "Cl.CreateKernel"); + + Random rand = new Random(); + int[] input = (from i in Enumerable.Range(0, 100) select (int)rand.Next()).ToArray(); + int[] output = new int[100]; + + var buffIn = _context.CreateBuffer(input, MemFlags.ReadOnly); + var buffOut = _context.CreateBuffer(output, MemFlags.WriteOnly); + int IntPtrSize = Marshal.SizeOf(typeof(IntPtr)); + error = Cl.SetKernelArg(kernel, 0, (IntPtr)IntPtrSize, buffIn); + error |= Cl.SetKernelArg(kernel, 1, (IntPtr)IntPtrSize, buffOut); + LogError(error, "Cl.SetKernelArg"); + CommandQueue cmdQueue = Cl.CreateCommandQueue(_context, _device, (CommandQueueProperties)0, out error); + LogError(error, "Cl.CreateCommandQueue"); + Event clevent; + error = Cl.EnqueueNDRangeKernel(cmdQueue, kernel, 2, null, new[]{(IntPtr)100,(IntPtr)1}, null, 0, null, out clevent); + LogError(error, "Cl.EnqueueNDRangeKernel"); + error = Cl.Finish(cmdQueue); + LogError(error, "Cl.Finih"); + error = Cl.EnqueueReadBuffer(cmdQueue, buffOut, OpenCL.Net.Bool.True, 0, 100, output, 0, null, out clevent); + LogError(error, "Cl.EnqueueReadBuffer"); + error = Cl.Finish(cmdQueue); + LogError(error, "Cl.Finih"); + + + Cl.ReleaseKernel(kernel); + Cl.ReleaseCommandQueue(cmdQueue); + Cl.ReleaseMemObject(buffIn); + Cl.ReleaseMemObject(buffOut); + sb.Append("
");
+          for(int i = 0; i != 100; i++) {
+            sb.Append(input[i] + " % 42 = " + output[i] + "
"); + } + sb.Append("
"); + } + sb.Append(""); + response.Body = Encoding.UTF8.GetBytes(sb.ToString()); + return response; + } + return new HTTPResponse(501); + } + + public HTTPResponse PostProcessing(HTTPResponse response) + { + throw new NotImplementedException(); + } + + public void SetParameters(Dictionary parameters) + { + _parameters = parameters; + } + } +} \ No newline at end of file diff --git a/HTTPRequest.cs b/HTTPRequest.cs index 232dee7..37c01e4 100644 --- a/HTTPRequest.cs +++ b/HTTPRequest.cs @@ -7,10 +7,14 @@ namespace DNWS { public class HTTPRequest { + public const string METHOD_POST = "POST"; + public const string METHOD_GET = "GET"; + public const string METHOD_DELETE = "DELETE"; + public const string METHOD_OPTIONS = "OPTIONS"; protected String _url; protected String _filename; - protected static Dictionary _propertyListDictionary = null; - protected static Dictionary _requestListDictionary = null; + protected Dictionary _propertyListDictionary = null; + protected Dictionary _requestListDictionary = null; protected String _body; @@ -57,11 +61,15 @@ public HTTPRequest(String request) _status = 401; return; } - if (!statusLine[0].ToLower().Equals("get")) + if (statusLine[0].ToLower().Equals("get")) { - _method = "GET"; - } else if(!statusLine[0].ToLower().Equals("post")) { - _method = "POST"; + _method = METHOD_GET; + } else if(statusLine[0].ToLower().Equals("post")) { + _method = METHOD_POST; + } else if(statusLine[0].ToLower().Equals("delete")) { + _method = METHOD_DELETE; + } else if(statusLine[0].ToLower().Equals("options")) { + _method = METHOD_OPTIONS; } else { _status = 501; return; @@ -81,7 +89,7 @@ public HTTPRequest(String request) if (parts.Length > 1) { String[] requestParts = Regex.Split(parts[1], "[=]"); if(requestParts.Length > 1) { - _requestListDictionary.Add(requestParts[0], requestParts[1]); + AddRequest(requestParts[0], requestParts[1]); } } } @@ -94,14 +102,18 @@ public HTTPRequest(String request) if(pair.Length == 1) { // handle post body if(pair[0].Length > 1) { //FIXME, this is a quick hack Dictionary _bodys = pair[0].Split('&').Select(x => x.Split('=')).ToDictionary(x => x[0].ToLower(), x => x[1]); - _requestListDictionary = _requestListDictionary.Concat(_bodys).ToDictionary(x=>x.Key, x=>x.Value); + foreach(KeyValuePair entry in _bodys) { + if(!_requestListDictionary.ContainsKey(entry.Key)) { + AddRequest(entry.Key, entry.Value); + } + } } } else { // Length == 2, GET url request - addProperty(pair[0], pair[1]); + AddProperty(pair[0], pair[1]); } } } - public String getPropertyByKey(String key) + public String GetPropertyByKey(String key) { if(_propertyListDictionary.ContainsKey(key.ToLower())) { return _propertyListDictionary[key.ToLower()]; @@ -109,7 +121,7 @@ public String getPropertyByKey(String key) return null; } } - public String getRequestByKey(String key) + public String GetRequestByKey(String key) { if(_requestListDictionary.ContainsKey(key.ToLower())) { return _requestListDictionary[key.ToLower()]; @@ -118,13 +130,13 @@ public String getRequestByKey(String key) } } - public void addProperty(String key, String value) + public void AddProperty(String key, String value) { - _propertyListDictionary[key.ToLower()] = value; + _propertyListDictionary.Add(key.ToLower(), value.TrimEnd('\r', '\n')); } - public void addRequest(String key, String value) + public void AddRequest(String key, String value) { - _requestListDictionary[key.ToLower()] = value; + _requestListDictionary.Add(key.ToLower(), value.TrimEnd('\r', '\n')); } } } \ No newline at end of file diff --git a/HTTPResponse.cs b/HTTPResponse.cs index 6f744d4..ecd91c1 100644 --- a/HTTPResponse.cs +++ b/HTTPResponse.cs @@ -10,14 +10,14 @@ namespace DNWS public class HTTPResponse { protected int _status = 404; - public int status + public int Status { get { return _status; } set { _status = value; } } protected byte[] _body; - public byte[] body + public byte[] Body { get { return _body; } set { _body = value; } @@ -25,13 +25,40 @@ public byte[] body protected string _type = "text/html"; - public string type + public string Type { get { return _type; } set { _type = value; } } - public String header + Dictionary _customHeader = new Dictionary(); + + public Dictionary CustomHeader + { + get + { + return _customHeader; + } + set + { + _customHeader = value; + } + } + + public bool AddCustomHeader(string key, string value) + { + if(_customHeader.ContainsKey(key)) { + return false; + } + _customHeader.Add(key, value); + return true; + } + public void SetBody(string msg) + { + Body = Encoding.UTF8.GetBytes(msg); + } + + public String Header { get { @@ -41,6 +68,12 @@ public String header case 200: headerResponse.Append("200 OK"); break; + case 201: + headerResponse.Append("201 Created"); + break; + case 301: + headerResponse.Append("301 Moved Permanently"); + break; case 400: headerResponse.Append("400 Bad Request"); break; @@ -59,9 +92,15 @@ public String header } headerResponse.Append("\r\n"); - headerResponse.Append("Content-Type: ").Append(type).Append("\r\n"); + headerResponse.Append("Content-Type: ").Append(Type).Append("\r\n"); headerResponse.Append("Connection: close\r\n"); headerResponse.Append("Server: DNWS 1.0\r\n"); + headerResponse.Append("Access-Control-Allow-Origin: *\r\n"); + headerResponse.Append("Access-Control-Allow-Headers: Content-Type, X-session \r\n"); + headerResponse.Append("Access-Control-Allow-Methods: GET, POST, OPTIONS, DELETE\r\n"); + foreach(KeyValuePair entry in _customHeader) { + headerResponse.Append(entry.Key).Append(": ").Append(entry.Value).Append("\r\n"); + } headerResponse.Append("\r\n"); return headerResponse.ToString(); } diff --git a/IPlugin.cs b/IPlugin.cs index cf7606b..25eadea 100644 --- a/IPlugin.cs +++ b/IPlugin.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; namespace DNWS { @@ -8,4 +9,9 @@ public interface IPlugin HTTPResponse GetResponse(HTTPRequest request); } + public interface IPluginWithParameters : IPlugin + { + void SetParameters(Dictionary parameters); + } + } \ No newline at end of file diff --git a/Kernel.cl b/Kernel.cl new file mode 100644 index 0000000..eea8abc --- /dev/null +++ b/Kernel.cl @@ -0,0 +1,5 @@ +__kernel void answer(__global int* a, __global int* b) +{ + int id = get_global_id(0); + b[id] = a[id] % 42; +} \ No newline at end of file diff --git a/OXPlugin.cs b/OXPlugin.cs index 63754de..5aaf7ec 100644 --- a/OXPlugin.cs +++ b/OXPlugin.cs @@ -589,7 +589,7 @@ public HTTPResponse GetResponse(HTTPRequest request) } } } - response.body = Encoding.UTF8.GetBytes(sb.ToString()); + response.Body = Encoding.UTF8.GetBytes(sb.ToString()); return response; } diff --git a/Program.cs b/Program.cs index 3fad0a2..76ca2d1 100644 --- a/Program.cs +++ b/Program.cs @@ -38,51 +38,130 @@ static void Main(string[] args) } } - /// - /// HTTP processor will process each http request - /// + public class PluginInfo + { + protected string _path; + protected string _type; + protected bool _preprocessing; + protected bool _postprocessing; + protected IPlugin _reference; + protected Dictionary _parameters; - public class HTTPProcessor + public string path + { + get { return _path;} + set {_path = value;} + } + public string type + { + get { return _type;} + set {_type = value;} + } + public bool preprocessing + { + get { return _preprocessing;} + set {_preprocessing = value;} + } + public bool postprocessing + { + get { return _postprocessing;} + set {_postprocessing = value;} + } + public IPlugin reference + { + get { return _reference;} + set {_reference = value;} + } + + public Dictionary parameters + { + get {return _parameters;} + set {_parameters = value;} + } + + } + + public class PluginManager { - protected class PluginInfo + private static PluginManager _instance = null; + private Dictionary plugins = null; + private Program _parent; + + private PluginManager() { - protected string _path; - protected string _type; - protected bool _preprocessing; - protected bool _postprocessing; - protected IPlugin _reference; - public string path - { - get { return _path;} - set {_path = value;} - } - public string type - { - get { return _type;} - set {_type = value;} - } - public bool preprocessing - { - get { return _preprocessing;} - set {_preprocessing = value;} + } + + private void SetParent(Program parent) + { + _parent = parent; + } + + /* Singletron + */ + public static PluginManager GetInstance(Program parent) + { + if (_instance == null) { + _instance = new PluginManager(); } - public bool postprocessing + _instance.SetParent(parent); + return _instance; + } + + public Dictionary Plugins + { + get { - get { return _postprocessing;} - set {_postprocessing = value;} + return plugins; } - public IPlugin reference + } + + public void LoadConfiguration(IEnumerable sections) + { + if (plugins == null) { - get { return _reference;} - set {_reference = value;} + plugins = new Dictionary(); + foreach (ConfigurationSection section in sections) + { + PluginInfo pi = new PluginInfo(); + Dictionary parameters = null; + pi.path = section["Path"]; + pi.type = section["Class"]; + pi.preprocessing = section["Preprocessing"].ToLower().Equals("true"); + pi.postprocessing = section["Postprocessing"].ToLower().Equals("true"); + foreach(ConfigurationSection parameter in section.GetSection("Parameters").GetChildren()) { + if (parameters == null) parameters = new Dictionary(); + parameters[parameter.Key] = parameter.Value; + } + try { + if(parameters != null) { + IPluginWithParameters ip = (IPluginWithParameters)Activator.CreateInstance(Type.GetType(pi.type)); + ip.SetParameters(parameters); + pi.reference = (IPlugin) ip; + } else { + pi.reference = (IPlugin)Activator.CreateInstance(Type.GetType(pi.type)); + } + } catch (Exception ex) { + _parent.Log("Error loading plugin " + pi.path + " with error " + ex); + continue; + } + plugins[section["Path"]] = pi; + _parent.Log("Plugin " + pi.path + " loaded."); + } } } + } + /// + /// HTTP processor will process each http request + /// + + public class HTTPProcessor + { // Get config from config manager, e.g., document root and port protected string ROOT = Program.Configuration["DocumentRoot"]; protected Socket _client; protected Program _parent; - protected Dictionary plugins; + protected PluginManager PM; /// /// Constructor, set the client socket and parent ref, also init stat hash @@ -93,18 +172,9 @@ public HTTPProcessor(Socket client, Program parent) { _client = client; _parent = parent; - plugins = new Dictionary(); // load plugins - var sections = Program.Configuration.GetSection("Plugins").GetChildren(); - foreach(ConfigurationSection section in sections) { - PluginInfo pi = new PluginInfo(); - pi.path = section["Path"]; - pi.type = section["Class"]; - pi.preprocessing = section["Preprocessing"].ToLower().Equals("true"); - pi.postprocessing = section["Postprocessing"].ToLower().Equals("true"); - pi.reference = (IPlugin) Activator.CreateInstance(Type.GetType(pi.type)); - plugins[section["Path"]] = pi; - } + PM = PluginManager.GetInstance(_parent); + PM.LoadConfiguration(Program.Configuration.GetSection("Plugins").GetChildren()); } /// @@ -118,31 +188,39 @@ protected HTTPResponse getFile(String path) // Guess the content type from file extension string fileType = "text/html"; - if (path.ToLower().EndsWith("jpg") || path.ToLower().EndsWith("jpeg")) + if (path.ToLower().EndsWith(".jpg") || path.ToLower().EndsWith(".jpeg")) { fileType = "image/jpeg"; } - if (path.ToLower().EndsWith("png")) + else if (path.ToLower().EndsWith(".png")) { fileType = "image/png"; } + else if (path.ToLower().EndsWith(".js")) + { + fileType = "application/javascript"; + } + else if (path.ToLower().EndsWith(".css")) + { + fileType = "text/css"; + } // Try to read the file, if not found then 404, otherwise, 500. try { response = new HTTPResponse(200); - response.type = fileType; - response.body = System.IO.File.ReadAllBytes(path); + response.Type = fileType; + response.Body = System.IO.File.ReadAllBytes(path); } catch (FileNotFoundException ex) { response = new HTTPResponse(404); - response.body = Encoding.UTF8.GetBytes("

404 Not found

" + ex.Message); + response.Body = Encoding.UTF8.GetBytes("

404 Not found

" + ex.Message); } catch (Exception ex) { response = new HTTPResponse(500); - response.body = Encoding.UTF8.GetBytes("

500 Internal Server Error

" + ex.Message); + response.Body = Encoding.UTF8.GetBytes("

500 Internal Server Error

" + ex.Message); } return response; @@ -154,21 +232,22 @@ protected HTTPResponse getFile(String path) public void Process() { NetworkStream ns = new NetworkStream(_client); - string requestStr = ""; + StringBuilder requestStr = new StringBuilder(); HTTPRequest request = null; HTTPResponse response = null; byte[] bytes = new byte[1024]; int bytesRead; + // Read all request do { bytesRead = ns.Read(bytes, 0, bytes.Length); - requestStr += Encoding.UTF8.GetString(bytes, 0, bytesRead); + requestStr.Append(Encoding.UTF8.GetString(bytes, 0, bytesRead)); } while (ns.DataAvailable); - request = new HTTPRequest(requestStr); - request.addProperty("RemoteEndPoint", _client.RemoteEndPoint.ToString()); + request = new HTTPRequest(requestStr.ToString()); + request.AddProperty("RemoteEndPoint", _client.RemoteEndPoint.ToString()); // We can handle only GET now if(request.Status != 200) { @@ -177,15 +256,19 @@ public void Process() else { bool processed = false; + //FIXME, this seem duplicate with HTTPRequest + string[] requestUrls = request.Url.Split("/"); + string[] paths = requestUrls[1].Split("?"); // pre processing - foreach(KeyValuePair plugininfo in plugins) { + foreach(KeyValuePair plugininfo in PM.Plugins) { if(plugininfo.Value.preprocessing) { plugininfo.Value.reference.PreProcessing(request); } } // plugins - foreach(KeyValuePair plugininfo in plugins) { - if(request.Filename.StartsWith(plugininfo.Key)) { + foreach(KeyValuePair plugininfo in PM.Plugins) { + if(paths[0].Equals(plugininfo.Key, StringComparison.InvariantCultureIgnoreCase)) { + //if(request.Url.StartsWith("/" + plugininfo.Key)) { response = plugininfo.Value.reference.GetResponse(request); processed = true; } @@ -194,24 +277,24 @@ public void Process() if(!processed) { if (request.Filename.Equals("")) { - response = getFile(ROOT + "/index.html"); + response = getFile(ROOT + "/" + request.Url + "/index.html"); } else { - response = getFile(ROOT + "/" + request.Filename); + response = getFile(ROOT + "/" + request.Url); } } // post processing pipe - foreach(KeyValuePair plugininfo in plugins) { + foreach(KeyValuePair plugininfo in PM.Plugins) { if(plugininfo.Value.postprocessing) { response = plugininfo.Value.reference.PostProcessing(response); } } } // Generate response - ns.Write(Encoding.UTF8.GetBytes(response.header), 0, response.header.Length); - if(response.body != null) { - ns.Write(response.body, 0, response.body.Length); + ns.Write(Encoding.UTF8.GetBytes(response.Header), 0, response.Header.Length); + if(response.Body != null) { + ns.Write(response.Body, 0, response.Body.Length); } // Shuting down diff --git a/StatPlugin.cs b/StatPlugin.cs index aec7606..160264d 100644 --- a/StatPlugin.cs +++ b/StatPlugin.cs @@ -27,7 +27,7 @@ public void PreProcessing(HTTPRequest request) statDictionary[request.Url] = 1; } } - public HTTPResponse GetResponse(HTTPRequest request) + public virtual HTTPResponse GetResponse(HTTPRequest request) { HTTPResponse response = null; StringBuilder sb = new StringBuilder(); @@ -38,7 +38,7 @@ public HTTPResponse GetResponse(HTTPRequest request) } sb.Append(""); response = new HTTPResponse(200); - response.body = Encoding.UTF8.GetBytes(sb.ToString()); + response.Body = Encoding.UTF8.GetBytes(sb.ToString()); return response; } diff --git a/StateAPIPlugin.cs b/StateAPIPlugin.cs new file mode 100644 index 0000000..da60c37 --- /dev/null +++ b/StateAPIPlugin.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Newtonsoft.Json; + +namespace DNWS +{ + // We use all the functionality from StatPlugin (so we inherit from it), just reimplement + // how we will response to client; + + class StatAPIPlugin : StatPlugin, IPlugin + { + + //Response model, this make it easier to shape the output + public class StatusResponse + { + private string _url; + private int _count; + public string Url { + get + { + return _url; + } + set + { + if(value == null || value == "") { + return; + } + _url = value; + } + } + public int Count { + get + { + return _count; + } + set + { + _count = (value < 0)? 0 : value; + } + } + + public StatusResponse(string url, int count) + { + Url = url; + Count = count; + } + } + + public override HTTPResponse GetResponse(HTTPRequest request) + { + // Check the request method first. + if(request.Method == "GET") { + HTTPResponse response = null; + // Create new list of response model ,this depend on output format; + List responseList = new List(); + // Fill in response model list + foreach (KeyValuePair entry in statDictionary) + { + responseList.Add(new StatusResponse(entry.Key, entry.Value)); + } + // Set response status and type + response = new HTTPResponse(200); + response.Type = "application/json"; + // Convert response model into json string, then byte array; + string resp = JsonConvert.SerializeObject(responseList); + response.Body = Encoding.UTF8.GetBytes(resp); + return response; + } + return new HTTPResponse(501); + } + } +} \ No newline at end of file diff --git a/TwitterAPI.md b/TwitterAPI.md new file mode 100644 index 0000000..73abebd --- /dev/null +++ b/TwitterAPI.md @@ -0,0 +1,96 @@ +# Twitter API Document + +## Note +1. All parameters will be passed through x-www-form-urlencoded *FIXME this should be implemented in JSON payload* +2. Session is maintained in the HTTP header *FIXME this should be cookie* + +## User +### URL: /twitter/user/ +**POST**: create new user + +- Required: None +- Parameters: username and password +- Return status: + - 201: user added + - 500: adding error + - 400: username/password missing + +### URL: /twitterapi/login +**POST**: login into an account + +- Required: None +- Parameters: username and password +- Return status: + - 201: login successfully, return {"Session": sessionValue} + - 500: can't create session + - 404: invalid username/password + - 400: username/password missing + +### URL: /twitterapi/logout +**POST**: logout from an account + +- Required: a valid session +- Parameters: None +- Return status: + - 200: logout successfully + - 500: logout error + +## Tweet +### URL: /twitter/ +**GET**: get following timeline + +- Required: a valid session +- Parameters: None +- Return status: + - 200: timeline avaliable, return [ {"TwitterId": id, "Message": Message Text, "User": username, "DateCreated": timestamp} ] + - 404: no post in timeline + - 500: internal error + +### URL: /twitter/tweet/ +**GET**: get list of user's tweets (i.e., timeline) + +- Required: a valid session +- Parameters: None +- Return status: + - 200: timeline avaliable, return [ {"TwitterId": id, "Message": Message Text, "User": username, "DateCreated": timestamp} ] + - 404: no post in timeline + - 500: internal error + +**POST**: post new tweet + +- Required: a valid session +- Parameters: message +- Return status: + - 201: post successfully + - 400: message missing + - 500: internal error + +**DELETE**: delete a tweet *FIXME not implemented yet* + +## Following +### URL: /twitter/following/ +**GET**: get list of user's following + +- Required: a valid session +- Parameters: None +- Return status: + - 200: following list, return [ {"FollowingId": id, "Name": username} ] + - 404: no following + +**POST**: follow a user + +- Required: a valid session +- Parameters: followingname +- Return status: + - 201: following successfully, return [ {"FollowingId": id, "Name": username} ] + - 404: no following + - 500: can't add following + +**DELETE**: unfollow a user + +- Required: a valid session +- Parameters: followingname +- Return status: + - 200: unfollowing successfully, return [ {"FollowingId": id, "Name": username} ] + - 404: no following + - 502: can't remove following \ No newline at end of file diff --git a/TwitterAPIPlugin.cs b/TwitterAPIPlugin.cs new file mode 100644 index 0000000..2f2da94 --- /dev/null +++ b/TwitterAPIPlugin.cs @@ -0,0 +1,328 @@ +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.Linq; +using System; +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace DNWS +{ + public class TwitterAPIPlugin : TwitterPlugin, IPlugin + { + private const string USER_ACTION = "user"; + private const string TWEET_ACTION = "tweet"; + private const string LOGIN_ACTION = "login"; + private const string LOGOUT_ACTION = "logout"; + private const string FOLLOWING_ACTION = "following"; + private string session; + private string action; + private string requestMethod; + private const string HTTP_GET = HTTPRequest.METHOD_GET; + private const string HTTP_POST = HTTPRequest.METHOD_POST; + private const string HTTP_DELETE = HTTPRequest.METHOD_DELETE; + private const string HTTP_OPTIONS = HTTPRequest.METHOD_OPTIONS; + + protected bool IsAction(string _action) + { + return _action.ToLower().Equals(action.ToLower()); + } + protected bool IsMethod(string _method) + { + return requestMethod.ToLower().Equals(_method.ToLower()); + } + protected Func IsNOE = String.IsNullOrEmpty; + public new HTTPResponse GetResponse(HTTPRequest request) + { + HTTPResponse response = new HTTPResponse(200); + StringBuilder sb = new StringBuilder(); + session = request.GetPropertyByKey("X-session"); + string[] urlToken = request.Url.Split("/"); + if (urlToken.Length > 2) + { + action = urlToken[2]; + } + else + { + action = null; + } + requestMethod = request.Method; + string username = request.GetRequestByKey("username"); + string password = request.GetRequestByKey("password"); + if(IsMethod(HTTP_OPTIONS)) { + return response; + } + + if (IsAction(USER_ACTION)) + { + if (IsMethod(HTTP_POST)) + { + if (!IsNOE(username) && !IsNOE(password)) + { + try + { + Twitter.AddUser(username, password); + response.Status = 201; + } + catch (Exception ex) + { + response.SetBody(ex.Message); + response.Status = 500; + } + } + else + { + response.SetBody("Username and password required"); + response.Status = 400; + return response; + } + } + } + else if (IsAction(LOGIN_ACTION)) + { + if (IsMethod(HTTP_POST)) + { + if (!IsNOE(username) && !IsNOE(password)) + { + if (Twitter.IsValidUser(username, password)) + { + string newSession = Twitter.GenSession(username); + if (!IsNOE(newSession)) + { + response.Status = 201; + dynamic jobj = new JObject(); + jobj.Session = newSession; + response.Type = "application/json"; + string resp = JsonConvert.SerializeObject(jobj); + response.Body = Encoding.UTF8.GetBytes(resp); + return response; + } + response.SetBody("Can't create session"); + response.Status = 500; + return response; + } + else + { + response.SetBody("Invalid username/password"); + response.Status = 404; + return response; + } + } + else + { + response.SetBody("Username and password required"); + response.Status = 400; + return response; + } + + } + } + else if (!IsNOE(session)) + { + User user = Twitter.GetUserFromSession(session); + if (user == null) + { + response.SetBody("User not found, please login again"); + response.Status = 404; + return response; + } + Twitter twitter = new Twitter(user.Name); + if (IsNOE(action)) + { + if (IsMethod(HTTP_GET)) + { + try + { + response = new HTTPResponse(200); + response.Type = "application/json"; + string resp = JsonConvert.SerializeObject(twitter.GetFollowingTimeline()); + if (IsNOE(resp)) + { + response.SetBody("No post in timline"); + response.Status = 404; + return response; + } + response.Body = Encoding.UTF8.GetBytes(resp); + return response; + } + catch (Exception ex) + { + response.SetBody(ex.Message); + response.Status = 500; + return response; + } + } + } + else if (IsAction(LOGOUT_ACTION)) + { + if (IsMethod(HTTP_POST)) + { + try { + Twitter.RemoveSession(user.Name); + response.Status = 200; + return response; + } + catch (Exception ex) + { + response.SetBody(ex.Message); + response.Status = 500; + return response; + } + } + } + else if (IsAction(TWEET_ACTION)) + { + if (IsMethod(HTTP_GET)) + { + try + { + response = new HTTPResponse(200); + response.Type = "application/json"; + string resp = null; + if(IsNOE(urlToken[3])) { + resp = JsonConvert.SerializeObject(twitter.GetTimeline(user)); + } + else + { + User aUser = Twitter.GetUserFromName(urlToken[3]); + if(aUser == null) { + response.SetBody("User not found"); + response.Status = 404; + return response; + } + resp = JsonConvert.SerializeObject(twitter.GetTimeline(aUser)); + } + if (IsNOE(resp)) + { + response.SetBody("No post in timline"); + response.Status = 404; + return response; + } + response.Body = Encoding.UTF8.GetBytes(resp); + return response; + } + catch (Exception ex) + { + response.SetBody(ex.Message); + response.Status = 500; + return response; + } + } + else if (IsMethod(HTTP_POST)) + { + try + { + string message = request.GetRequestByKey("message"); + if (IsNOE(message)) { + response.SetBody("Message required"); + response.Status = 400; + return response; + } + Tweet tweet = new Tweet(); + tweet.User = user.Name; + tweet.Message = message; + tweet.DateCreated = DateTime.Now; + using (var context = new TweetContext()) + { + context.Tweets.Add(tweet); + context.SaveChanges(); + } + response.Status = 201; + return response; + } + catch (Exception ex) + { + response.SetBody(ex.Message); + response.Status = 500; + return response; + } + + } + else if (IsMethod(HTTP_DELETE)) + { + try { + throw (new NotImplementedException()); + } catch (Exception ex) { + response.SetBody(ex.Message); + response.Status = 501; + return response; + } + } + } + else if (IsAction(FOLLOWING_ACTION)) + { + if (IsMethod(HTTP_GET)) + { + response = new HTTPResponse(200); + response.Type = "application/json"; + string resp = JsonConvert.SerializeObject(user.Following); + if (resp == null) + { + response.SetBody("No following"); + response.Status = 404; + return response; + } + response.Body = Encoding.UTF8.GetBytes(resp); + return response; + } + else if (IsMethod(HTTP_POST)) + { + string following = request.GetRequestByKey("followingname"); + try + { + twitter.AddFollowing(following); + response = new HTTPResponse(201); + response.Type = "application/json"; + string resp = JsonConvert.SerializeObject(user.Following); + if (resp == null) + { + response.SetBody("No folowing"); + response.Status = 404; + return response; + } + response.Body = Encoding.UTF8.GetBytes(resp); + return response; + } + catch (Exception ex) + { + response.Status = 400; + response.SetBody(ex.Message); + return response; + + } + } + else if (IsMethod(HTTP_DELETE)) + { + string following = request.GetRequestByKey("followingname"); + try + { + twitter.RemoveFollowing(following); + response = new HTTPResponse(200); + response.Type = "application/json"; + string resp = JsonConvert.SerializeObject(user.Following); + if (resp == null) + { + response.SetBody("No following"); + response.Status = 404; + return response; + } + response.Body = Encoding.UTF8.GetBytes(resp); + return response; + } + catch (Exception ex) + { + response.Status = 400; + response.SetBody(ex.Message); + return response; + + } + } + } + } + response.Status = 400; + return response; + } + } +} \ No newline at end of file diff --git a/TwitterPlugin.cs b/TwitterPlugin.cs index 9b48930..3bb9c0a 100644 --- a/TwitterPlugin.cs +++ b/TwitterPlugin.cs @@ -5,6 +5,7 @@ using System.Linq; using System; using System.ComponentModel.DataAnnotations.Schema; +using System.Security.Cryptography; namespace DNWS { @@ -18,6 +19,7 @@ class User public int UserId { get; set; } public string Name { get; set; } public string Password { get; set; } + public string Session {get; set;} public List Following { get; set; } // Bug in SQLite implemention in EF7, no FK! } class Tweet @@ -49,12 +51,16 @@ public Twitter(string name) { throw new Exception("User name is required"); } - user = GetUser(name); + user = Twitter.GetUserFromName(name); } public string GetUsername() { return user.Name; } + public String GetSession() + { + return user.Session; + } public void RemoveFollowing(string followingName) { if (user == null) @@ -91,12 +97,13 @@ public void AddFollowing(string followingName) user.Following = new List(); } List followings = user.Following.Where(b => b.Name == followingName).ToList(); - if (followings.Count > 0) return; - Following following = new Following(); - following.Name = followingName; - user.Following.Add(following); - context.Users.Update(user); - context.SaveChanges(); + if (followings.Count == 0) { + Following following = new Following(); + following.Name = followingName; + user.Following.Add(following); + context.Users.Update(user); + context.SaveChanges(); + } } } public List GetTimeline(User aUser) @@ -137,9 +144,10 @@ public List GetFollowingTimeline() } foreach (Following following in followings) { - User followingUser = GetUser(following.Name); + User followingUser = Twitter.GetUserFromName(following.Name); timeline.AddRange(GetTimeline(followingUser)); } + timeline.AddRange(GetTimeline(user)); } timeline = timeline.OrderBy(b => b.DateCreated).ToList(); return timeline; @@ -160,6 +168,7 @@ public void PostTweet(string message) context.SaveChanges(); } } + public static void AddUser(string name, string password) { User user = new User(); @@ -189,8 +198,69 @@ public static bool IsValidUser(string name, string password) } return false; } + public static User GetUserFromSession(string session) + { + using (var context = new TweetContext()) + { + try + { + List users = context.Users.Where(b => b.Session.Equals(session)).Include(b => b.Following).ToList(); + return users[0]; + } + catch (Exception) + { + return null; + } + } + } - private User GetUser(string name) + public static void RemoveSession(string username) + { + using (var context = new TweetContext()) + { + List users = context.Users.Where(b => b.Name.Equals(username)).ToList(); + User aUser = users[0]; + aUser.Session = null; + context.Users.Update(aUser); + context.SaveChanges(); + } + } + public static string GenSession(string username) + { + try + { + using (MD5 md5 = MD5.Create()) + { + md5.Initialize(); + md5.ComputeHash(Encoding.UTF8.GetBytes(username + DateTime.Now.ToString())); + // It's annoying that toString is not working here. + StringBuilder sbhash = new StringBuilder(); + byte[] hash = md5.Hash; + for (int i = 0; i < hash.Length; i++) + { + sbhash.Append(hash[i].ToString("x2")); + } + string newSession = sbhash.ToString(); + //Update session. + using (var context = new TweetContext()) + { + List users = context.Users.Where(b => b.Name.Equals(username)).ToList(); + User aUser = users[0]; + aUser.Session = newSession; + context.Users.Update(aUser); + context.SaveChanges(); + } + return newSession; + } + + } + catch (Exception) + { + return null; + } + + } + public static User GetUserFromName(string name) { using (var context = new TweetContext()) { @@ -205,7 +275,6 @@ private User GetUser(string name) } } } - } public class TwitterPlugin : IPlugin { @@ -224,11 +293,13 @@ private StringBuilder GenTimeline(Twitter twitter, StringBuilder sb) sb.Append("Say something
"); sb.Append("
"); sb.Append(""); + sb.Append(""); sb.Append("
"); sb.Append("
"); sb.Append("Follow someone
"); sb.Append("
"); sb.Append(""); + sb.Append(""); sb.Append("
"); sb.Append("
"); sb.Append(String.Format("

{0}'s timeline


", twitter.GetUsername())); @@ -258,14 +329,14 @@ protected StringBuilder GenLoginPage(StringBuilder sb) { sb.Append("

Login

"); sb.Append("
"); - sb.Append("Username:
"); + sb.Append("Username:
"); sb.Append("Password:
"); sb.Append("
"); sb.Append("
"); sb.Append("


"); sb.Append("

New user

"); sb.Append("
"); - sb.Append("Username:
"); + sb.Append("Username:
"); sb.Append("Password:
"); sb.Append("
"); sb.Append("
"); @@ -277,40 +348,28 @@ public HTTPResponse GetResponse(HTTPRequest request) { HTTPResponse response = new HTTPResponse(200); StringBuilder sb = new StringBuilder(); - string user = request.getRequestByKey("user"); - string password = request.getRequestByKey("password"); - string action = request.getRequestByKey("action"); - string following = request.getRequestByKey("following"); - string message = request.getRequestByKey("message"); - if (user == null) // no user? show login screen - { - sb.Append("

Twitter

"); - sb = GenLoginPage(sb); - } - else + string session = request.GetRequestByKey("session"); + string action = request.GetRequestByKey("action").ToLower(); + if(action != null) action = action.ToLower(); + string username = request.GetRequestByKey("username"); + string password = request.GetRequestByKey("password"); + string following = request.GetRequestByKey("following"); + string message = request.GetRequestByKey("message"); + if (session == null) // no session? show login screen { - if (action == null) // No action? go to homepage - { - try - { - Twitter twitter = new Twitter(user); - sb.Append(String.Format("

{0}'s Twitter

", user)); - sb = GenTimeline(twitter, sb); - } - catch (Exception ex) - { - sb.Append(String.Format("Error [{0}], please go back to login page to try again", ex.Message)); - } + if (action == null) { + sb.Append("

Twitter

"); + sb = GenLoginPage(sb); } else { if (action.Equals("newuser")) { - if (user != null && password != null && user != "" && password != "") + if (username != null && password != null && username != "" && password != "") { try { - Twitter.AddUser(user, password); + Twitter.AddUser(username, password); sb.Append("User added successfully, please go back to login page to login"); } catch (Exception ex) @@ -321,11 +380,18 @@ public HTTPResponse GetResponse(HTTPRequest request) } else if (action.Equals("login")) { - if (user != null && password != null && user != "" && password != "") + if (username != null && password != null && username != "" && password != "") { - if (Twitter.IsValidUser(user, password)) + if (Twitter.IsValidUser(username, password)) { - sb.Append(String.Format("Welcome {0}, please go back to tweet page to begin", user)); + string newSession = Twitter.GenSession(username); + if(newSession != null) { + response.Status = 301; + response.AddCustomHeader("Location", "/twitter?session=" + newSession); + return response; + } + response.Status = 500; + return response; } else { @@ -333,39 +399,57 @@ public HTTPResponse GetResponse(HTTPRequest request) } } } - else + } + } + else // session is not null + { + User user = Twitter.GetUserFromSession(session); + if(user == null) { + response.Status = 404; + return response; + } + Twitter twitter = new Twitter(user.Name); + if (action == null) // No action? go to homepage + { + try + { + sb.Append(String.Format("

{0}'s Twitter

", user.Name)); + sb = GenTimeline(twitter, sb); + } + catch (Exception ex) { - Twitter twitter = new Twitter(user); - sb.Append(String.Format("

{0}'s Twitter

", user)); - if (action.Equals("following")) + sb.Append(String.Format("Error [{0}], please go back to login page to try again", ex.Message)); + } + } else { //action is not null + sb.Append(String.Format("

{0}'s Twitter

", user.Name)); + if (action.Equals("following")) + { + try { - try - { - twitter.AddFollowing(following); - sb = GenTimeline(twitter, sb); - } - catch (Exception ex) - { - sb.Append(String.Format("Error [{0}], please go back to login page to try again", ex.Message)); - } + twitter.AddFollowing(following); + sb = GenTimeline(twitter, sb); } - else if (action.Equals("tweet")) + catch (Exception ex) { - try - { - twitter.PostTweet(message); - sb = GenTimeline(twitter, sb); - } - catch (Exception ex) - { - Console.WriteLine(ex.ToString()); - sb.Append(String.Format("Error [{0}], please go back to login page to try again", ex.Message)); - } + sb.Append(String.Format("Error [{0}], please go back to login page to try again", ex.Message)); + } + } + else if (action.Equals("tweet")) + { + try + { + twitter.PostTweet(message); + sb = GenTimeline(twitter, sb); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + sb.Append(String.Format("Error [{0}], please go back to login page to try again", ex.Message)); } } } } - response.body = Encoding.UTF8.GetBytes(sb.ToString()); + response.Body = Encoding.UTF8.GetBytes(sb.ToString()); return response; } } diff --git a/client/.bowerrc b/client/.bowerrc new file mode 100644 index 0000000..5069c75 --- /dev/null +++ b/client/.bowerrc @@ -0,0 +1,4 @@ +{ + "directory": "app/bower_components", + "interactive": false +} diff --git a/client/LICENSE b/client/LICENSE new file mode 100644 index 0000000..b8de5aa --- /dev/null +++ b/client/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2010-2016 Google, Inc. http://angularjs.org + +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. + diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000..8d52ba4 --- /dev/null +++ b/client/README.md @@ -0,0 +1,295 @@ +# `angular-seed` — the seed for AngularJS apps + +This project is an application skeleton for a typical [AngularJS][angularjs] web app. You can use it +to quickly bootstrap your angular webapp projects and dev environment for these projects. + +The seed contains a sample AngularJS application and is preconfigured to install the Angular +framework and a bunch of development and testing tools for instant web development gratification. + +The seed app doesn't do much, just shows how to wire two controllers and views together. + + +## Getting Started + +To get you started you can simply clone the `angular-seed` repository and install the dependencies: + +### Prerequisites + +You need git to clone the `angular-seed` repository. You can get git from [here][git]. + +We also use a number of Node.js tools to initialize and test `angular-seed`. You must have Node.js +and its package manager (npm) installed. You can get them from [here][node]. + +### Clone `angular-seed` + +Clone the `angular-seed` repository using git: + +``` +git clone https://github.com/angular/angular-seed.git +cd angular-seed +``` + +If you just want to start a new project without the `angular-seed` commit history then you can do: + +``` +git clone --depth=1 https://github.com/angular/angular-seed.git +``` + +The `depth=1` tells git to only pull down one commit worth of historical data. + +### Install Dependencies + +We have two kinds of dependencies in this project: tools and Angular framework code. The tools help +us manage and test the application. + +* We get the tools we depend upon via `npm`, the [Node package manager][npm]. +* We get the Angular code via `bower`, a [client-side code package manager][bower]. +* In order to run the end-to-end tests, you will also need to have the + [Java Development Kit (JDK)][jdk] installed on your machine. Check out the section on + [end-to-end testing](#e2e-testing) for more info. + +We have preconfigured `npm` to automatically run `bower` so we can simply do: + +``` +npm install +``` + +Behind the scenes this will also call `bower install`. After that, you should find out that you have +two new folders in your project. + +* `node_modules` - contains the npm packages for the tools we need +* `app/bower_components` - contains the Angular framework files + +*Note that the `bower_components` folder would normally be installed in the root folder but +`angular-seed` changes this location through the `.bowerrc` file. Putting it in the `app` folder +makes it easier to serve the files by a web server.* + +### Run the Application + +We have preconfigured the project with a simple development web server. The simplest way to start +this server is: + +``` +npm start +``` + +Now browse to the app at [`localhost:8000/index.html`][local-app-url]. + + +## Directory Layout + +``` +app/ --> all of the source files for the application + app.css --> default stylesheet + components/ --> all app specific modules + version/ --> version related components + version.js --> version module declaration and basic "version" value service + version_test.js --> "version" value service tests + version-directive.js --> custom directive that returns the current app version + version-directive_test.js --> version directive tests + interpolate-filter.js --> custom interpolation filter + interpolate-filter_test.js --> interpolate filter tests + view1/ --> the view1 view template and logic + view1.html --> the partial template + view1.js --> the controller logic + view1_test.js --> tests of the controller + view2/ --> the view2 view template and logic + view2.html --> the partial template + view2.js --> the controller logic + view2_test.js --> tests of the controller + app.js --> main application module + index.html --> app layout file (the main html template file of the app) + index-async.html --> just like index.html, but loads js files asynchronously +karma.conf.js --> config file for running unit tests with Karma +e2e-tests/ --> end-to-end tests + protractor-conf.js --> Protractor config file + scenarios.js --> end-to-end scenarios to be run by Protractor +``` + + +## Testing + +There are two kinds of tests in the `angular-seed` application: Unit tests and end-to-end tests. + +### Running Unit Tests + +The `angular-seed` app comes preconfigured with unit tests. These are written in [Jasmine][jasmine], +which we run with the [Karma][karma] test runner. We provide a Karma configuration file to run them. + +* The configuration is found at `karma.conf.js`. +* The unit tests are found next to the code they are testing and have an `_test.js` suffix (e.g. + `view1_test.js`). + +The easiest way to run the unit tests is to use the supplied npm script: + +``` +npm test +``` + +This script will start the Karma test runner to execute the unit tests. Moreover, Karma will start +watching the source and test files for changes and then re-run the tests whenever any of them +changes. +This is the recommended strategy; if your unit tests are being run every time you save a file then +you receive instant feedback on any changes that break the expected code functionality. + +You can also ask Karma to do a single run of the tests and then exit. This is useful if you want to +check that a particular version of the code is operating as expected. The project contains a +predefined script to do this: + +``` +npm run test-single-run +``` + + + +### Running End-to-End Tests + +The `angular-seed` app comes with end-to-end tests, again written in [Jasmine][jasmine]. These tests +are run with the [Protractor][protractor] End-to-End test runner. It uses native events and has +special features for Angular applications. + +* The configuration is found at `e2e-tests/protractor-conf.js`. +* The end-to-end tests are found in `e2e-tests/scenarios.js`. + +Protractor simulates interaction with our web app and verifies that the application responds +correctly. Therefore, our web server needs to be serving up the application, so that Protractor can +interact with it. + +**Before starting Protractor, open a separate terminal window and run:** + +``` +npm start +``` + +In addition, since Protractor is built upon WebDriver, we need to ensure that it is installed and +up-to-date. The `angular-seed` project is configured to do this automatically before running the +end-to-end tests, so you don't need to worry about it. If you want to manually update the WebDriver, +you can run: + +``` +npm run update-webdriver +``` + +Once you have ensured that the development web server hosting our application is up and running, you +can run the end-to-end tests using the supplied npm script: + +``` +npm run protractor +``` + +This script will execute the end-to-end tests against the application being hosted on the +development server. + +**Note:** +Under the hood, Protractor uses the [Selenium Standalone Server][selenium], which in turn requires +the [Java Development Kit (JDK)][jdk] to be installed on your local machine. Check this by running +`java -version` from the command line. + +If JDK is not already installed, you can download it [here][jdk-download]. + + +## Updating Angular + +Since the Angular framework library code and tools are acquired through package managers (npm and +bower) you can use these tools to easily update the dependencies. Simply run the preconfigured +script: + +``` +npm run update-deps +``` + +This will call `npm update` and `bower update`, which in turn will find and install the latest +versions that match the version ranges specified in the `package.json` and `bower.json` files +respectively. + + +## Loading Angular Asynchronously + +The `angular-seed` project supports loading the framework and application scripts asynchronously. +The special `index-async.html` is designed to support this style of loading. For it to work you must +inject a piece of Angular JavaScript into the HTML page. The project has a predefined script to help +do this: + +``` +npm run update-index-async +``` + +This will copy the contents of the `angular-loader.js` library file into the `index-async.html` +page. You can run this every time you update the version of Angular that you are using. + + +## Serving the Application Files + +While Angular is client-side-only technology and it is possible to create Angular web apps that +do not require a backend server at all, we recommend serving the project files using a local +web server during development to avoid issues with security restrictions (sandbox) in browsers. The +sandbox implementation varies between browsers, but quite often prevents things like cookies, XHR, +etc to function properly when an HTML page is opened via the `file://` scheme instead of `http://`. + +### Running the App during Development + +The `angular-seed` project comes preconfigured with a local development web server. It is a Node.js +tool called [http-server][http-server]. You can start this web server with `npm start`, but you may +choose to install the tool globally: + +``` +sudo npm install -g http-server +``` + +Then you can start your own development web server to serve static files from a folder by running: + +``` +http-server -a localhost -p 8000 +``` + +Alternatively, you can choose to configure your own web server, such as Apache or Nginx. Just +configure your server to serve the files under the `app/` directory. + +### Running the App in Production + +This really depends on how complex your app is and the overall infrastructure of your system, but +the general rule is that all you need in production are the files under the `app/` directory. +Everything else should be omitted. + +Angular apps are really just a bunch of static HTML, CSS and JavaScript files that need to be hosted +somewhere they can be accessed by browsers. + +If your Angular app is talking to the backend server via XHR or other means, you need to figure out +what is the best way to host the static files to comply with the same origin policy if applicable. +Usually this is done by hosting the files by the backend server or through reverse-proxying the +backend server(s) and web server(s). + + +## Continuous Integration + +### Travis CI + +[Travis CI][travis] is a continuous integration service, which can monitor GitHub for new commits to +your repository and execute scripts such as building the app or running tests. The `angular-seed` +project contains a Travis configuration file, `.travis.yml`, which will cause Travis to run your +tests when you push to GitHub. + +You will need to enable the integration between Travis and GitHub. See the +[Travis website][travis-docs] for instructions on how to do this. + + +## Contact + +For more information on AngularJS please check out [angularjs.org][angularjs]. + + +[angularjs]: https://angularjs.org/ +[bower]: http://bower.io/ +[git]: https://git-scm.com/ +[http-server]: https://github.com/indexzero/http-server +[jasmine]: https://jasmine.github.io/ +[jdk]: https://wikipedia.org/wiki/Java_Development_Kit +[jdk-download]: http://www.oracle.com/technetwork/java/javase/downloads +[karma]: https://karma-runner.github.io/ +[local-app-url]: http://localhost:8000/index.html +[node]: https://nodejs.org/ +[npm]: https://www.npmjs.org/ +[protractor]: http://www.protractortest.org/ +[selenium]: http://docs.seleniumhq.org/ +[travis]: https://travis-ci.org/ +[travis-docs]: https://docs.travis-ci.com/user/getting-started diff --git a/client/app/app.config.js b/client/app/app.config.js new file mode 100644 index 0000000..eb91b30 --- /dev/null +++ b/client/app/app.config.js @@ -0,0 +1,32 @@ +'use strict'; + +angular. + module('twitterApp'). + config(['$locationProvider' ,'$routeProvider', + function config($locationProvider, $routeProvider) { + $locationProvider.hashPrefix('!'); + + $routeProvider. + when('/tweet/', { + template: '' + }). + when('/following/', { + template: '' + }). + when('/login/', { + template: '' + }). + when('/', { + template: '' + }). + otherwise('/'); + } + ]) + .run(function($rootScope, $location, $cookies){ + $rootScope.$on("$routeChangeStart", function(event, next, current) { + $rootScope.x_session = $cookies.get('x-session'); + if($rootScope.x_session == null) { + $location.path("/login/"); + } + }); + }) ; \ No newline at end of file diff --git a/client/app/app.css b/client/app/app.css new file mode 100644 index 0000000..48e881d --- /dev/null +++ b/client/app/app.css @@ -0,0 +1,25 @@ +/* app css stylesheet */ + +.menu { + list-style: none; + border-bottom: 0.1em solid black; + margin-bottom: 2em; + padding: 0 0 0.5em; +} + +.menu:before { + content: "["; +} + +.menu:after { + content: "]"; +} + +.menu > li { + display: inline; +} + +.menu > li + li:before { + content: "|"; + padding-right: 0.3em; +} diff --git a/client/app/app.module.js b/client/app/app.module.js new file mode 100644 index 0000000..a0512ea --- /dev/null +++ b/client/app/app.module.js @@ -0,0 +1,10 @@ +'use strict'; + +// Declare app level module which depends on views, and components +angular.module('twitterApp', [ + 'ngRoute', + 'homeList', + 'tweetList', + 'followingList', + 'loginPage', +]); \ No newline at end of file diff --git a/client/app/components/version/interpolate-filter.js b/client/app/components/version/interpolate-filter.js new file mode 100644 index 0000000..03bb198 --- /dev/null +++ b/client/app/components/version/interpolate-filter.js @@ -0,0 +1,9 @@ +'use strict'; + +angular.module('myApp.version.interpolate-filter', []) + +.filter('interpolate', ['version', function(version) { + return function(text) { + return String(text).replace(/\%VERSION\%/mg, version); + }; +}]); diff --git a/client/app/components/version/interpolate-filter_test.js b/client/app/components/version/interpolate-filter_test.js new file mode 100644 index 0000000..ff56c52 --- /dev/null +++ b/client/app/components/version/interpolate-filter_test.js @@ -0,0 +1,15 @@ +'use strict'; + +describe('myApp.version module', function() { + beforeEach(module('myApp.version')); + + describe('interpolate filter', function() { + beforeEach(module(function($provide) { + $provide.value('version', 'TEST_VER'); + })); + + it('should replace VERSION', inject(function(interpolateFilter) { + expect(interpolateFilter('before %VERSION% after')).toEqual('before TEST_VER after'); + })); + }); +}); diff --git a/client/app/components/version/version-directive.js b/client/app/components/version/version-directive.js new file mode 100644 index 0000000..74088f8 --- /dev/null +++ b/client/app/components/version/version-directive.js @@ -0,0 +1,9 @@ +'use strict'; + +angular.module('myApp.version.version-directive', []) + +.directive('appVersion', ['version', function(version) { + return function(scope, elm, attrs) { + elm.text(version); + }; +}]); diff --git a/client/app/components/version/version-directive_test.js b/client/app/components/version/version-directive_test.js new file mode 100644 index 0000000..4a59e11 --- /dev/null +++ b/client/app/components/version/version-directive_test.js @@ -0,0 +1,17 @@ +'use strict'; + +describe('myApp.version module', function() { + beforeEach(module('myApp.version')); + + describe('app-version directive', function() { + it('should print current version', function() { + module(function($provide) { + $provide.value('version', 'TEST_VER'); + }); + inject(function($compile, $rootScope) { + var element = $compile('')($rootScope); + expect(element.text()).toEqual('TEST_VER'); + }); + }); + }); +}); diff --git a/client/app/components/version/version.js b/client/app/components/version/version.js new file mode 100644 index 0000000..cb7a10f --- /dev/null +++ b/client/app/components/version/version.js @@ -0,0 +1,8 @@ +'use strict'; + +angular.module('myApp.version', [ + 'myApp.version.interpolate-filter', + 'myApp.version.version-directive' +]) + +.value('version', '0.1'); diff --git a/client/app/components/version/version_test.js b/client/app/components/version/version_test.js new file mode 100644 index 0000000..4ca6880 --- /dev/null +++ b/client/app/components/version/version_test.js @@ -0,0 +1,11 @@ +'use strict'; + +describe('myApp.version module', function() { + beforeEach(module('myApp.version')); + + describe('version service', function() { + it('should return current version', inject(function(version) { + expect(version).toEqual('0.1'); + })); + }); +}); diff --git a/client/app/following/following.html b/client/app/following/following.html new file mode 100644 index 0000000..39f7e54 --- /dev/null +++ b/client/app/following/following.html @@ -0,0 +1,10 @@ +
+

My Following

+ + Add new follow +
\ No newline at end of file diff --git a/client/app/following/following.js b/client/app/following/following.js new file mode 100644 index 0000000..6356c75 --- /dev/null +++ b/client/app/following/following.js @@ -0,0 +1,25 @@ +'use strict'; + +angular.module('followingList', ['ngRoute']) + .component('followingList', { + templateUrl: 'following/following.html', + controller: ['$http', '$rootScope', function TweetListController($http, $rootScope) { + var self = this; + + const requestOptions = { + headers: { 'X-session': $rootScope.x_session } //ref 600611006 + }; + self.sendFollow = function sendFollow(followingname) { + const data = "followingname=" + encodeURIComponent(followingname); + $http.post('http://localhost:8080/twitterapi/following/', data, requestOptions); + } + self.sendUnFollow = function sendUnFollow(followingname) { + $http.defaults.headers.delete = { 'X-session': $rootScope.x_session }; + const data = "followingname=" + encodeURIComponent(followingname); + $http.delete('http://localhost:8080/twitterapi/following/?' + data); + } + $http.get('http://localhost:8080/twitterapi/following/', requestOptions).then(function (response) { + self.followings = response.data; + }); + }] +}); \ No newline at end of file diff --git a/client/app/home/home.html b/client/app/home/home.html new file mode 100644 index 0000000..b278293 --- /dev/null +++ b/client/app/home/home.html @@ -0,0 +1,15 @@ +
+

Compose

+
+ +
+
+ +
+
+
+

Following timeline

+
    +
  • {{tweet.User}} : {{tweet.Message}}
  • +
+
\ No newline at end of file diff --git a/client/app/home/home.js b/client/app/home/home.js new file mode 100644 index 0000000..e0ddc56 --- /dev/null +++ b/client/app/home/home.js @@ -0,0 +1,40 @@ +'use strict'; + +angular.module('homeList', ['ngRoute']) + .component('homeList', { + templateUrl: 'home/home.html', + controller: ['$http', '$rootScope', function FollowingListController($http, $rootScope) { + var self = this; + + self.sendTweet = function sendTweet(message) { + const requestOptions = { + headers: { 'X-session': $rootScope.x_session } + }; + var data ="message=" + encodeURIComponent(message); + $http.post('http://localhost:8080/twitterapi/tweet/', data, requestOptions).then(function (response) { + $http.get('http://localhost:8080/twitterapi/', requestOptions).then(function (response) { + self.tweets = response.data; + self.tweets.forEach(function iterator(value, index, collection) { + value.Message = decodeURIComponent(value.Message); + }); + }); + }); + } + + self.getFollowingTimeline = function getFollowingTimeline() { + const requestOptions = { + headers: { 'X-session': $rootScope.x_session } + }; + + $http.get('http://localhost:8080/twitterapi/', requestOptions).then(function (response) { + self.tweets = response.data; + self.tweets.forEach(function iterator(value, index, collection) { + value.Message = decodeURIComponent(value.Message); + }); + }); + } + + self.getFollowingTimeline(); + + }] +}); \ No newline at end of file diff --git a/client/app/index.html b/client/app/index.html new file mode 100644 index 0000000..7eb653d --- /dev/null +++ b/client/app/index.html @@ -0,0 +1,42 @@ + + + + + + + + + + Twitter App + + + + + + + + + + + + + + + + + + + + +
+

Twitter

+ +
+
+ + diff --git a/client/app/login/login.html b/client/app/login/login.html new file mode 100644 index 0000000..238e719 --- /dev/null +++ b/client/app/login/login.html @@ -0,0 +1,12 @@ +
+

Login

+
+ +
+
+ +
+
+ +
+
\ No newline at end of file diff --git a/client/app/login/login.js b/client/app/login/login.js new file mode 100644 index 0000000..63f8536 --- /dev/null +++ b/client/app/login/login.js @@ -0,0 +1,29 @@ +'use strict'; + +angular.module('loginPage', ['ngRoute', 'ngCookies']) + .component('loginPage', { + templateUrl: 'login/login.html', + controller: ['$http','$cookies', '$window', '$rootScope', function loginPageController($http, $cookies, $window, $rootScope) { + var self = this; + self.cookies = $cookies; + self.checkXSessionAndRedirect = function getSession() + { + $rootScope.x_session = $cookies.get('x-session'); + if($rootScope.x_session != null) { + $window.location.href = "/"; + } + } + self.sendLogin = function sendLogin(username, password) + { + const requestOptions = { + headers: { 'Content-Type': 'application/x-www-form-urlencoded' } + }; + const data = "username=" + encodeURIComponent(username) + "&password=" + encodeURIComponent(password); + $http.post('http://localhost:8080/twitterapi/login/', data, requestOptions).then(function (response) { + $cookies.put('x-session', response.data.Session); + self.checkXSessionAndRedirect(); + }); + } + self.checkXSessionAndRedirect(); + }] +}); \ No newline at end of file diff --git a/client/app/tweet/tweet.html b/client/app/tweet/tweet.html new file mode 100644 index 0000000..9450c1e --- /dev/null +++ b/client/app/tweet/tweet.html @@ -0,0 +1,6 @@ +
+

User timeline

+
    +
  • {{tweet.User}} : {{tweet.Message}}
  • +
+
\ No newline at end of file diff --git a/client/app/tweet/tweet.js b/client/app/tweet/tweet.js new file mode 100644 index 0000000..160ffaf --- /dev/null +++ b/client/app/tweet/tweet.js @@ -0,0 +1,20 @@ +'use strict'; + +angular.module('tweetList', ['ngRoute']) + .component('tweetList', { + templateUrl: 'tweet/tweet.html', + controller: ['$http', '$rootScope', function TweetListController($http, $rootScope) { + var self = this; + + const requestOptions = { + headers: { 'X-session': $rootScope.x_session } + }; + + $http.get('http://localhost:8080/twitterapi/tweet/', requestOptions).then(function (response) { + self.tweets = response.data; + self.tweets.forEach(function iterator(value, index, collection) { + value.Message = decodeURIComponent(value.Message); + }); + }); + }] +}); \ No newline at end of file diff --git a/client/bower.json b/client/bower.json new file mode 100644 index 0000000..c620cef --- /dev/null +++ b/client/bower.json @@ -0,0 +1,17 @@ +{ + "name": "twitter app", + "description": "Simple twitter client in Angular", + "version": "0.0.1", + "homepage": "https://github.com/pruet/DNWS/", + "license": "GPL", + "private": true, + "dependencies": { + "angular": "~1.5.0", + "angular-route": "~1.5.0", + "angular-cookies" : "~1.5.0", + "angular-loader": "~1.5.0", + "angular-mocks": "~1.5.0", + "html5-boilerplate": "^5.3.0", + "bootstrap": "3.3.x" + } +} diff --git a/client/e2e-tests/protractor.conf.js b/client/e2e-tests/protractor.conf.js new file mode 100644 index 0000000..13c5cb6 --- /dev/null +++ b/client/e2e-tests/protractor.conf.js @@ -0,0 +1,22 @@ +//jshint strict: false +exports.config = { + + allScriptsTimeout: 11000, + + specs: [ + '*.js' + ], + + capabilities: { + 'browserName': 'chrome' + }, + + baseUrl: 'http://localhost:8000/', + + framework: 'jasmine', + + jasmineNodeOpts: { + defaultTimeoutInterval: 30000 + } + +}; diff --git a/client/e2e-tests/scenarios.js b/client/e2e-tests/scenarios.js new file mode 100644 index 0000000..240d5f6 --- /dev/null +++ b/client/e2e-tests/scenarios.js @@ -0,0 +1,42 @@ +'use strict'; + +/* https://github.com/angular/protractor/blob/master/docs/toc.md */ + +describe('my app', function() { + + + it('should automatically redirect to /view1 when location hash/fragment is empty', function() { + browser.get('index.html'); + expect(browser.getLocationAbsUrl()).toMatch("/view1"); + }); + + + describe('view1', function() { + + beforeEach(function() { + browser.get('index.html#!/view1'); + }); + + + it('should render view1 when user navigates to /view1', function() { + expect(element.all(by.css('[ng-view] p')).first().getText()). + toMatch(/partial for view 1/); + }); + + }); + + + describe('view2', function() { + + beforeEach(function() { + browser.get('index.html#!/view2'); + }); + + + it('should render view2 when user navigates to /view2', function() { + expect(element.all(by.css('[ng-view] p')).first().getText()). + toMatch(/partial for view 2/); + }); + + }); +}); diff --git a/client/karma.conf.js b/client/karma.conf.js new file mode 100644 index 0000000..7271e9f --- /dev/null +++ b/client/karma.conf.js @@ -0,0 +1,34 @@ +//jshint strict: false +module.exports = function(config) { + config.set({ + + basePath: './app', + + files: [ + 'bower_components/angular/angular.js', + 'bower_components/angular-route/angular-route.js', + 'bower_components/angular-mocks/angular-mocks.js', + 'components/**/*.js', + 'view*/**/*.js' + ], + + autoWatch: true, + + frameworks: ['jasmine'], + + browsers: ['Chrome'], + + plugins: [ + 'karma-chrome-launcher', + 'karma-firefox-launcher', + 'karma-jasmine', + 'karma-junit-reporter' + ], + + junitReporter: { + outputFile: 'test_out/unit.xml', + suite: 'unit' + } + + }); +}; diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..0386d51 --- /dev/null +++ b/client/package.json @@ -0,0 +1,24 @@ +{ + "name": "angular-seed", + "private": true, + "version": "0.0.0", + "description": "A starter project for AngularJS", + "repository": "https://github.com/angular/angular-seed", + "license": "MIT", + "devDependencies": { + "bower": "^1.7.7", + "http-server": "^0.9.0", + "jasmine-core": "^2.4.1" + }, + "scripts": { + "postinstall": "bower install", + + "update-deps": "npm update", + "postupdate-deps": "bower update", + + "prestart": "npm install", + "start": "http-server -a localhost -p 8000 -c-1 ./app", + + "update-index-async": "node -e \"var fs=require('fs'),indexFile='app/index-async.html',loaderFile='app/bower_components/angular-loader/angular-loader.min.js',loaderText=fs.readFileSync(loaderFile,'utf-8').split(/sourceMappingURL=angular-loader.min.js.map/).join('sourceMappingURL=bower_components/angular-loader/angular-loader.min.js.map'),indexText=fs.readFileSync(indexFile,'utf-8').split(/\\/\\/@@NG_LOADER_START@@[\\s\\S]*\\/\\/@@NG_LOADER_END@@/).join('//@@NG_LOADER_START@@\\n'+loaderText+' //@@NG_LOADER_END@@');fs.writeFileSync(indexFile,indexText);\"" + } +} diff --git a/config.json b/config.json index b0bb0f2..56653de 100644 --- a/config.json +++ b/config.json @@ -1,10 +1,17 @@ -{ +{ "DocumentRoot": ".", "Port": "8080", "ThreadPoolSize" : "10", "AllowThreadModel" : "Pool|Multi|Single", "ThreadModel" : "Single", "Plugins" : [ + { + "Path" : "stat", + "Class" : "DNWS.StatPlugin", + "Preprocessing" : "true", + "Postprocessing" : "false" , + "Singleton" : "false" + }, { "Path" : "stat", "Class" : "DNWS.StatPlugin", @@ -18,6 +25,16 @@ "Preprocessing" : "false", "Postprocessing" :"false" }, + { + "Path" : "gpu", + "Class" : "DNWS.GPUPlugin", + "Preprocessing" : "false", + "Postprocessing" :"false", + "Parameters" : { + "AllowDeviceType" : "Gpu|Cpu|All|Default|Accelerator", + "DeviceType" : "Gpu" + } + }, { "Path" : "delay", "Class" : "DNWS.DelayPlugin", @@ -29,6 +46,12 @@ "Class" : "DNWS.TwitterPlugin", "Preprocessing" : "false", "Postprocessing" :"false" + }, + { + "Path" : "twitterapi", + "Class" : "DNWS.TwitterAPIPlugin", + "Preprocessing" : "false", + "Postprocessing" :"false" } ] }