use System.Net use System.Web use System.IO use System.Reflection use System.Drawing use System.Diagnostics use Fasterflect namespace CherryCobbler class Server """The Server class wraps the HttpListener class and provides an easy method for extending.""" var listenForNextRequest as System.Threading.AutoResetEvent is shared var _handlers = Dictionary() var _registeredMethods = Dictionary() var _configParameters = Dictionary() var _configInstance = Dictionary() var _errorResult = "Unknown Page:{page}
Query:{query}" def applicationPath as String return Path.getDirectoryName(Environment.getCommandLineArgs[0]) to ! cue init base.init _httpListener = HttpListener() .listenForNextRequest = System.Threading.AutoResetEvent(false) #handle the favicon.icon by default ico = Icon.extractAssociatedIcon(Environment.getCommandLineArgs[0]) using fs = FileStream(Path.combine(.applicationPath, "favicon.ico"),FileMode.Create) ico.save(fs) var _httpListener as HttpListener #Only if attempting to process ASP.Net Pages pro prefix from var as String? def start if String.isNullOrEmpty(.prefix) throw InvalidOperationException("No prefix has been specified") _httpListener.prefixes.clear _httpListener.prefixes.add(.prefix) try _httpListener.start catch #ex as Exception .attemptAddAddress(.prefix to !) _isRunning = true System.Threading.ThreadPool.queueUserWorkItem(ref .listen) def attemptAddAddress(prefix as String) .addAddress(prefix, Environment.userDomainName to !, Environment.userName to !) def addAddress(address as String, domain as String, user as String) args = "http add urlacl url=[address] user=[domain]\\[user]" psi = ProcessStartInfo("netsh",args) psi.verb = "runas" psi.createNoWindow = true psi.windowStyle = ProcessWindowStyle.Hidden psi.useShellExecute = true Process.start(psi).waitForExit def stop is internal _httpListener.stop .isRunning = false var _isRunning as bool pro isRunning as bool get return _isRunning set _isRunning = value def listen(state as Object?) while _httpListener.isListening _httpListener.beginGetContext(AsyncCallback(ref .listenerCallback), _httpListener) .listenForNextRequest.waitOne def listenerCallback(result as IAsyncResult) listener = result.asyncState to HttpListener? if listener == nil return context as HttpListenerContext? = nil try context = listener.endGetContext(result) catch ex as Exception System.Diagnostics.Debug.writeLine(ex.toString) finally .listenForNextRequest.set if context == nil return .processRequest(context) #region Register def registerType(type as Type) for mi in type.getMethods att = Attribute.getCustomAttribute(mi,ExposedAttribute) to ExposedAttribute? if att == nil continue if not mi.isStatic continue invoker = MethodInfoExtensions.delegateForCallMethod(mi) config = att.makeWebMethodConfig _configParameters[config] = mi.getParameters to ! _configInstance[config] = nil _registeredMethods[config ] = invoker to ! def registerInstance(obj as Object) for mi in obj.getType.getMethods att = Attribute.getCustomAttribute(mi,ExposedAttribute) to ExposedAttribute? if att == nil continue if mi.isStatic continue trace mi invoker = MethodInfoExtensions.delegateForCallMethod(mi) config = att.makeWebMethodConfig _configParameters[config] = mi.getParameters to ! _configInstance[config] = obj _registeredMethods[config ] = invoker to ! def registerWebMethodHandler(config as WebMethodConfig, handler as WebMethodHandler) _handlers[config] = handler var _allowDirectoryBrowse = false def registerStaticPaths(allowDirectoryBrowse as bool, paths as vari String) _allowDirectoryBrowse = allowDirectoryBrowse .registerWebMethodHandler(WebMethodConfig("/favicon.ico",webMethodType=WebMethodType.StaticContent), ref .static) for path in paths if path == "/" throw Exception("Static Paths cannot be root path.") .registerWebMethodHandler(WebMethodConfig(path,webMethodType=WebMethodType.StaticContent), ref .static) #endregion def getRequestPostData(request as HttpListenerRequest) as String if not request.hasEntityBody return "" using postdata = request.inputStream using reader = StreamReader(postdata, request.contentEncoding) return reader.readToEnd to ! def getFileBytesWithTildeExpansion(path as String,virtualPath as String) as uint8[] if .isText(Path.getExtension(path.toLower) to !) contents = File.readAllText(path,Encoding.utf8) contents = contents.replace(" src=\"~/", " src=\"/" + virtualPath+ "/") contents = contents.replace(" src='~/", " src='/" + virtualPath + "/") contents = contents.replace(" href=\"~/", " href=\"/" + virtualPath + "/") contents = contents.replace(" href='~/", " href='/" + virtualPath + "/") return System.Text.UTF8Encoding.utf8.getBytes(contents) to ! else return File.readAllBytes(path) def isText(ext as String) as bool branch ext on ".css" or ".htm" or ".html" or ".asp" or ".aspx" or ".asmx" or ".xml" or ".json" or ".js" or ".cobra" or ".txt" or ".cs" or ".vb" or ".py" or ".xaml" or ".sgm" or ".sgml" return true else return false def static(req as WebMethodRequest) as WebMethodResult path = Path.combine(.applicationPath,req.page.trimStart(c'/')) virtualPath = Path.getDirectoryName(req.page).trimEnd(c'/',c'\\').trimStart(c'/',c'\\') #trace req, path, req.page, .getRequestPostData(req.context.request to !), virtualPath if File.exists(path) return WebMethodResult(bytes=.getFileBytesWithTildeExpansion(path,virtualPath)) else if _allowDirectoryBrowse and Directory.exists(path) return WebMethodResult(text=.getDirectoryBrowse(path,req)) else return WebMethodResult(text=_errorResult.replace("{page}",req.page).replace("{query}",req.query)) def getDirectoryBrowse(path as String, req as WebMethodRequest) as String sb = StringBuilder("Files at '[req.page]'
    ") for dir in Directory.getDirectories(path) dirName =dir.replace(path,"").trimStart(c'/',c'\\') trace path, dir, dirName, req.page sb.append("
  • [dirName]/
  • \r\n") for file in Directory.getFiles(path) fileName = Path.getFileName(file) sb.append("
  • [fileName]
  • ") sb.append("
") return sb.toString def processRequest(context as HttpListenerContext?) if context == nil return #.sendText(context to !, "Request received!") #response = context.response page = context.request.url.localPath to ! query = context.request.url.query.replace("?","") req = WebMethodRequest(page, query,context to !) #attempt to find the page handler #The default result is assigned to the error result. result = WebMethodResult(text=_errorResult.replace("{page}",req.page).replace("{query}",req.query)) handled = false for config, handler in _handlers if .isPageHandler(config, page) result = handler(req) handled = true break if not handled for config, invoker in _registeredMethods if .isPageHandler(config, page) params = .buildParameters(query, _configParameters[config]) if params == nil System.Console.writeLine("Matching config, but parameters didn't match or were incorrectly formed.") continue #We had a matching config, but the parameters didn't match or were incorrectly formed. res = invoker(_configInstance[config], params) if res.typeOf == String result = WebMethodResult(text = res.toString) else result = WebMethodResult(bytes=res to uint8[]) handled = true break .sendResult(context to !, result) #_asphost.parse_code(page,query,inout sw) trace context, page,query def isPageHandler(config as WebMethodConfig, page as String) as bool if config.uriPath == page return true if Glob.like(page, config.uriPath) return true if config.webMethodType == WebMethodType.StaticContent and page.startsWith(config.uriPath) return true return false def buildParameters(query as String, parameters as ParameterInfo[]) as Object[]? result = List() queryParts = query.split(c'&') if query.trim == "" and parameters.length ==0 return result.toArray if queryParts.length <> parameters.length System.Console.writeLine("Mismatched Parameters Length: Query: [queryParts.length] vs Function: [parameters.length]") return nil for iParameter in queryParts.length parameterParts = queryParts[iParameter].split(c'=') if parameterParts.length < 2 System.Console.writeLine("Parameter is not name/value pair: Parameter: [queryParts[iParameter]]") return nil if parameterParts[0].toLower == parameters[iParameter].name.toLower result.add(Convert.changeType(parameterParts[1], parameters[iParameter].parameterType)) else System.Console.writeLine("Parameter Name doesn't match: Query Name: [parameterParts[0].toLower] vs Parameter Name: [parameters[iParameter].name.toLower]") return nil return result.toArray def sendResult(context as HttpListenerContext, msg as WebMethodResult) if msg.text <> nil byteArr = System.Text.UTF8Encoding.utf8.getBytes(msg.text) context.response.outputStream.write(byteArr,0,byteArr.length) else if msg.bytes <> nil context.response.outputStream.write(msg.bytes,0,msg.bytes.length) context.response.outputStream.flush context.response.close