diff --git a/.cvsignore b/.cvsignore deleted file mode 100644 index 5da3204..0000000 --- a/.cvsignore +++ /dev/null @@ -1,7 +0,0 @@ -Makefile -Makefile.in -aclocal.m4 -config.log -config.status -configure -autom4te.cache diff --git a/ChangeLog b/ChangeLog index 525ade3..0941dc2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,6 +18,28 @@ * tools/mono-asp-apps/mono-asp-apps: added +2007-10-29 Robert Jordan + + * src/Mono.WebServer.FastCgi/WorkerRequest.cs: + Implement IsSecure (). + + * src/Mono.WebServer.FastCgi/Server.cs: + Use Type.IsAssignableFrom (). + +2007-10-28 Robert Jordan + + * src/Mono.WebServer.FastCgi/ConfigurationManager.*: + Implement an additional configuration source: environment. + + * src/Mono.WebServer.FastCgi/Request.cs: + Take directory index (default documents) into account. + +2007-10-28 Robert Jordan + + * src/Mono.WebServer.FastCgi/Request.cs: + Compute PATH_INFO, PATH_TRANSLATED, SCRIPT_NAME, + SCRIPT_FILENAME when running under Apache. + 2007-10-26 Wade Berrier * configure.in: @@ -37,6 +59,76 @@ exceptions in the constructor, by closing the connection when Exception happens. +2007-10-22 Robert Jordan + + * man/*: Generate fastcgi-mono-server(1). + * doc/*: Temporarily fix for `make distcheck'. + * src/Mono.WebServer.FastCgi/Makefile.am: Cleanups. + +2007-10-22 Robert Jordan + + * src/*/*.am : Fix `make distcheck'. + +2007-10-22 Robert Jordan + + * src/Mono.WebServer.FastCgi/Request.cs: + Reverted temporary hack for mod_fastcgi. + + * src/Mono.WebServer.FastCgi/ConfigurationManager.cs: + The XML default settings have less precedence. + +2007-10-22 Robert Jordan + + * src/Mono.WebServer.Apache/Mono.WebServer.Apache.sources: Create. + * src/Mono.WebServer.Apache/SecurityConfiguration.cs: + Get rid of MODMONO. + +2007-10-22 Robert Jordan + + * src/Makefile.am: FastCGI -> FastCgi. + + * src/Mono.WebServer.FastCgi/Makefile.am: + * src/Mono.WebServer.FastCgi/*.sources: + * src/Mono.WebServer.FastCgi/AssemblyInfo.cs.in: + Create. + + * src/Mono.WebServer.FastCgi/ConfigurationManager.xml: + Add some default values to be able to get rid of + the exe.config file. Comment out the automapping settings. + + * src/Mono.WebServer.FastCgi/ConfigurationManager.cs: + Implement ImportSettings (). Support for default settings + specified in ConfigurationManaged.xml. + + * src/Mono.WebServer.FastCgi/main.cs: + * src/Mono.WebServer.FastCgi/Responder.cs: + Take the ApplicationManager + out of the build until its automapping issues are fixed. + + * src/Mono.WebServer.FastCgi/server.cs: Rename to main.cs + +2007-10-22 Robert Jordan + + * src/Mono.WebServer.FastCgi: Import Brian's files from his google + repository. Flatten hierarchies to adhere to mono's standards. + Disintegrate the Mono.FastCgi assembly. + +2007-10-22 Robert Jordan + + * configure.in, scripts/Makefile.am: Reflect changes. + * src/Makefile.am: Reflect changes. + * src/Mono.WebServer.Apache/main.cs: Remove XSP-related code. + * src/Mono.WebServer.XSP/main.cs: Remove ModMono-related code. + * src/Mono.WebServer.Apache/Makefile.am: Create from .Makefile.am. + * src/Mono.WebServer.XSP/Makefile.am: Create from Makefile.am. + * src/server.cs: Copy as main.cs to Mono.WebServer.Apache and + Mono.WebServer.XSP + * src/ecurity.cs: Move to Mono.WebServer.XSP/SecurityConfiguration.cs + * src/ModMono*.cs: Move to Mono.WebServer.Apache. + * src/Mono.WebServer.Apache: Create. + * src/Mono.WebServer.FastCgi: Create. + * src/Mono.WebServer.XSP: Create. + 2007-10-03 Juraj Skripsky * src/ModMonoRequest.cs: put the ModMonoConfig struct in charge to diff --git a/configure.in b/configure.in index 9dd117e..240cf37 100644 --- a/configure.in +++ b/configure.in @@ -1,5 +1,5 @@ AC_PREREQ(2.57) -AC_INIT(src/server.cs) +AC_INIT(src/Mono.WebServer.XSP/main.cs) AC_CANONICAL_SYSTEM AM_INIT_AUTOMAKE(xsp, 1.2.5) AM_MAINTAINER_MODE @@ -104,13 +104,17 @@ AC_OUTPUT([ man/Makefile scripts/Makefile src/Makefile - src/AssemblyInfo.cs - src/AssemblyInfoModMono.cs src/Mono.WebServer/AssemblyInfo.cs src/Mono.WebServer/AssemblyInfo2.cs src/Mono.WebServer/Makefile src/Mono.WebServer/xsp.pc src/Mono.WebServer/xsp-2.pc + src/Mono.WebServer.Apache/Makefile + src/Mono.WebServer.Apache/AssemblyInfo.cs + src/Mono.WebServer.FastCgi/Makefile + src/Mono.WebServer.FastCgi/AssemblyInfo.cs + src/Mono.WebServer.XSP/Makefile + src/Mono.WebServer.XSP/AssemblyInfo.cs test/Makefile test/1.1/Makefile test/1.1/authtest/Makefile diff --git a/docs/Makefile.am b/docs/Makefile.am index 8b13789..90b30df 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -1 +1 @@ - +CLEANFILES = Mono.WebServer.xml diff --git a/man/Makefile.am b/man/Makefile.am index 0436558..1da96a9 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -1,6 +1,9 @@ -man_MANS = xsp.1 mod-mono-server.1 dbsessmgr.1 asp-state.1 +man_MANS = xsp.1 mod-mono-server.1 fastcgi-mono-server.1 dbsessmgr.1 asp-state.1 EXTRA_DIST = xsp.1.in dbsessmgr.1.in asp-state.1.in mono-asp-apps.1.in -CLEANFILES = mod-mono-server.1 asp-state.1 mono-asp-apps.1 +CLEANFILES = mod-mono-server.1 fastcgi-mono-server.1 asp-state.1 1 mono-asp-apps.1 mod-mono-server.1: xsp.1 cp xsp.1 $@ + +fastcgi-mono-server.1: xsp.1 + cp xsp.1 $@ diff --git a/man/xsp.1.in b/man/xsp.1.in index 8b3793a..6ecd1b7 100644 --- a/man/xsp.1.in +++ b/man/xsp.1.in @@ -1,7 +1,7 @@ .\" .\" xsp/mod-mono-server manual page. .\" (c) Copyright 2003 Ximian, Inc. -.\" (c) Copyright 2004 Novell, Inc. +.\" (c) Copyright 2004-2007 Novell, Inc. .\" Author: .\" Gonzalo Paniagua Javier (gonzalo@ximian.com) .\" @@ -9,15 +9,20 @@ .SH NAME XSP \- Mono ASP.NET Web Server (xsp and xsp2) .SH SYNOPSIS -.B mono xsp.exe +.B xsp [options] .PP or .PP -.B mono mod-mono-server.exe +.B mod-mono-server +[options] +.PP +or +.PP +.B fastcgi-mono-server [options] .SH DESCRIPTION -XSP and mod-mono-server are both hosts for ASP.NET-based applications. +XSP, mod-mono-server and fastcgi-mono-server are hosts for ASP.NET-based applications. .PP If run as `xsp', the process provides a minimalistic web server which hosts the ASP.NET runtime and can be used to test and debug web @@ -25,9 +30,11 @@ applications that use the System.Web facilities in Mono. This server is most convenient for testing and running small sites, does not offer everything a production web server offers. .PP -`mod-mono-server' is an ASP.NET runtime host that can communicate with -another web server (at the time of this writing Apache 1.3 and Apache -2.0 were supported). This mechanism is better used for high-traffic +`mod-mono-server' and 'fastcgi-mono-server' are both ASP.NET runtimes hosts +that can communicate with another web server (at the time of this writing, +Apache 1.3-2.2 were supported through mod_mono and several other web +servers through FastCGI). +This mechanism is better used for high-traffic servers or production systems, since it can integrate with the main HTTP server and leverage all of the optimizations and extensions of an existing server, while providing the ASP.NET runtime. You can choose @@ -47,10 +54,10 @@ requests. By default XSP listens on port 8080 and mod-mono-server has no default. AppSettings key name: MonoServerPort .TP -.I \-\-filename file (mod-mono-server only) +.I \-\-filename file (mod-mono-server and fastcgi-mono-server) The unix socket file name to listen on. -Default value: /tmp/mod_mono_server -AppSettings key name: UnixSocketFileName +Default value: /tmp/mod_mono_server (fastcgi-mono-server: /tmp/fastcgi-mono-server) +AppSettings key name: UnixSocketFileName (fastcgi-mono-server: MonoUnixSocket) .TP .I \-\-root PATH The root directory for XSP. The default is the directory where XSP is @@ -253,7 +260,8 @@ file in the XSP samples directory. .SH AUTHORS The Mono XSP server was written by Gonzalo Paniagua Javier -(gonzalo@ximian.com). +(gonzalo@ximian.com). Fastcgi-mono-server was written by +Brian Nickel . .SH ENVIRONMENT VARIABLES .TP .I "MONO_ASPNET_NODELETE" diff --git a/scripts/Makefile.am b/scripts/Makefile.am index 6028a4e..87e9570 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -1,12 +1,12 @@ EXTRA_DIST = script.in -bin2_scripts_real = xsp2 mod-mono-server2 +bin2_scripts_real = xsp2 mod-mono-server2 fastcgi-mono-server2 if NET_2_0 bin2_scripts = $(bin2_scripts_real) tool2_scripts = asp-state2 dbsessmgr2 endif -bin1_scripts = xsp mod-mono-server +bin1_scripts = xsp mod-mono-server fastcgi-mono-server tool_scripts = asp-state dbsessmgr bin_SCRIPTS = $(bin1_scripts) $(bin2_scripts) $(tool_scripts) $(tool2_scripts) diff --git a/src/.cvsignore b/src/.cvsignore deleted file mode 100644 index 225dd96..0000000 --- a/src/.cvsignore +++ /dev/null @@ -1,4 +0,0 @@ -Makefile -Makefile.in -AssemblyInfo*.cs -*.mdb diff --git a/src/Makefile.am b/src/Makefile.am index f7344a5..b61b5d0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,89 +1,7 @@ -SUBDIRS=Mono.WebServer -builddir=$(top_builddir)/src - -MCSFLAGS= -debug+ -debug:full -nologo -nowarn:618 $(WEBTRACING) - -xspdir = $(prefix)/lib/xsp/1.0 -modmonoserverdir = $(prefix)/lib/xsp/1.0 -xsp2dir = $(prefix)/lib/xsp/2.0 -modmonoserver2dir = $(prefix)/lib/xsp/2.0 - -GACUTIL1=$(GACUTIL) -package 1.0 -if NET_2_0 -XSP2_EXE = xsp2.exe -MODMONOSERVER2_EXE = mod-mono-server2.exe -GACUTIL2=$(GACUTIL) -package 2.0 -endif - -if !XSP_ONLY -modmonoserver_SCR = mod-mono-server.exe -modmonoserver2_SCR = $(MODMONOSERVER2_EXE) -endif - -noinst_SCRIPTS=xsp.exe xsp.exe $(XSP2_EXE) $(modmonoserver_SCR) $(modmonoserver2_SCR) - -CLEANFILES = *.exe *.mdb - -# -xsp_references= -r:System.Web.dll -r:Mono.WebServer/Mono.WebServer.dll -r:Mono.Security.dll -xsp2_references= -r:System.Web.dll -r:System.Configuration.dll -r:Mono.WebServer/Mono.WebServer2.dll -r:Mono.Security.dll -if PLATFORM_WIN32 -modmono_references= -lib:"$(prefix)/lib" -r:Mono.WebServer/Mono.WebServer.dll \ - -r:System.Web.dll -r:Mono.Posix.dll -r:Mono.Security.dll -modmono2_references= -lib:"$(prefix)/lib" -r:Mono.WebServer/Mono.WebServer2.dll \ - -r:System.Web.dll -r:Mono.Posix.dll -r:Mono.Security.dll +if XSP_ONLY +SUBDIRS=Mono.WebServer Mono.WebServer.XSP else -modmono_references= -r:System.Web.dll -r:Mono.WebServer/Mono.WebServer.dll -r:Mono.Posix.dll -r:Mono.Security.dll -modmono2_references= -r:System.Web.dll -r:System.Configuration.dll -r:Mono.WebServer/Mono.WebServer2.dll \ - -r:Mono.Posix.dll -r:Mono.Security.dll +SUBDIRS=Mono.WebServer Mono.WebServer.XSP Mono.WebServer.Apache Mono.WebServer.FastCgi endif -xsp_sources = server.cs security.cs - -xsp_build_sources = $(addprefix $(srcdir)/, $(xsp_sources)) AssemblyInfo.cs - -modmono_only = ModMonoRequest.cs \ - ModMonoWorkerRequest.cs \ - ModMonoApplicationHost.cs \ - ModMonoTCPWebSource.cs \ - ModMonoRequestBroker.cs \ - ModMonoWebSource.cs \ - ModMonoWorker.cs - -modmono_sources = $(modmono_only) $(xsp_sources) -modmono_build_sources = $(addprefix $(srcdir)/, $(modmono_sources)) AssemblyInfoModMono.cs -EXTRA_DIST = $(xsp_sources) $(modmono_only) AssemblyInfo.cs.in AssemblyInfoModMono.cs.in mono.pub mono.snk - - -xsp.exe: $(xsp_build_sources) - $(MCS) $(MCSFLAGS) $(xsp_references) /out:$@ $(xsp_build_sources) - $(SN) -q -R $(builddir)/$@ $(srcdir)/mono.snk - -mod-mono-server.exe: $(modmono_build_sources) - $(MCS) $(MCSFLAGS) $(modmono_references) /d:MODMONO_SERVER /out:$@ $(modmono_build_sources) - $(SN) -q -R $(builddir)/$@ $(srcdir)/mono.snk - -xsp2.exe: $(xsp_build_sources) - $(GMCS) -d:NET_2_0 $(MCSFLAGS) $(xsp2_references) /out:$@ $(xsp_build_sources) - $(SN) -q -R $(builddir)/$@ $(srcdir)/mono.snk - -mod-mono-server2.exe: $(modmono_build_sources) - $(GMCS) -d:NET_2_0 $(MCSFLAGS) $(modmono2_references) /d:MODMONO_SERVER /out:$@ $(modmono_build_sources) - $(SN) -q -R $(builddir)/$@ $(srcdir)/mono.snk - -install-data-local: - for i in xsp.exe $(modmonoserver_SCR) ; do \ - $(GACUTIL1) $(GACUTIL_FLAGS) -i $(top_builddir)/src/$$i ; \ - done - -#if NET_2_0 - for i in xsp2.exe $(modmonoserver2_SCR) ; do \ - $(GACUTIL2) $(GACUTIL_FLAGS) -i $(top_builddir)/src/$$i ; \ - done -#endif - -uninstall-local: - -for i in xsp mod-mono-server xsp2 mod-mono-server2 ; do \ - $(GACUTIL) $(GACUTIL_FLAGS) -u $$(basename $$i .exe) ; \ - done - +EXTRA_DIST = mono.pub mono.snk diff --git a/src/ModMonoApplicationHost.cs b/src/Mono.WebServer.Apache/ApplicationHost.cs similarity index 100% rename from src/ModMonoApplicationHost.cs rename to src/Mono.WebServer.Apache/ApplicationHost.cs diff --git a/src/AssemblyInfoModMono.cs.in b/src/Mono.WebServer.Apache/AssemblyInfo.cs.in similarity index 82% rename from src/AssemblyInfoModMono.cs.in rename to src/Mono.WebServer.Apache/AssemblyInfo.cs.in index a0800ce..99f3d6a 100644 --- a/src/AssemblyInfoModMono.cs.in +++ b/src/Mono.WebServer.Apache/AssemblyInfo.cs.in @@ -1,9 +1,9 @@ -// AssemblyInfoModMono.cs.in: +// AssemblyInfo.cs.in: // // Authors: // Gonzalo Paniagua Javier (gonzalo@ximian.com) // -// Copyright (c) 2002,2003,2004,2005,2006 Novell, Inc. (http://www.novell.com) +// Copyright (c) 2002-2007 Novell, Inc. (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -29,9 +29,9 @@ using System.Reflection; using System.Runtime.CompilerServices; [assembly: AssemblyVersion("@XSP_VERSION@")] -[assembly: AssemblyTitle ("ModMono-XSP Server")] -[assembly: AssemblyDescription ("Minimalistic web server for testing System.Web")] -[assembly: AssemblyCopyright ("(c) 2002-2006 Novell, Inc.")] +[assembly: AssemblyTitle ("Mono.WebServer.Apache")] +[assembly: AssemblyDescription ("Mod_mono backend for XSP")] +[assembly: AssemblyCopyright ("(c) 2002-2007 Novell, Inc.")] [assembly: AssemblyCompany ("Novell, Inc.")] [assembly: AssemblyDelaySign(true)] [assembly: AssemblyKeyFile("@top_srcdir@/src/mono.pub")] diff --git a/src/Mono.WebServer.Apache/Makefile.am b/src/Mono.WebServer.Apache/Makefile.am new file mode 100644 index 0000000..89d8b7f --- /dev/null +++ b/src/Mono.WebServer.Apache/Makefile.am @@ -0,0 +1,51 @@ +builddir=$(top_builddir)/src/Mono.WebServer.Apache + +MCSFLAGS= -debug+ -debug:full -nologo -nowarn:618 $(WEBTRACING) + +GACUTIL1=$(GACUTIL) -package 1.0 + +if NET_2_0 +MODMONOSERVER2_EXE = mod-mono-server2.exe +GACUTIL2=$(GACUTIL) -package 2.0 +endif + +noinst_SCRIPTS=mod-mono-server.exe $(MODMONOSERVER2_EXE) + +CLEANFILES = *.exe *.mdb + +if PLATFORM_WIN32 +references= -lib:"$(prefix)/lib" -r:../Mono.WebServer/Mono.WebServer.dll \ + -r:System.Web.dll -r:Mono.Posix.dll -r:Mono.Security.dll +references2= -lib:"$(prefix)/lib" -r:../Mono.WebServer/Mono.WebServer2.dll \ + -r:System.Web.dll -r:Mono.Posix.dll -r:Mono.Security.dll +else +references= -r:System.Web.dll -r:../Mono.WebServer/Mono.WebServer.dll -r:Mono.Posix.dll -r:Mono.Security.dll +references2= -r:System.Web.dll -r:System.Configuration.dll -r:../Mono.WebServer/Mono.WebServer2.dll \ + -r:Mono.Posix.dll -r:Mono.Security.dll +endif + +sources = $(shell cat $(srcdir)/Mono.WebServer.Apache.sources) + +build_sources = $(addprefix $(srcdir)/, $(sources)) AssemblyInfo.cs + +EXTRA_DIST = $(sources) AssemblyInfo.cs.in Mono.WebServer.Apache.sources + + +mod-mono-server.exe: $(sources) + $(MCS) $(MCSFLAGS) $(references) /out:$@ $(build_sources) + $(SN) -q -R $(builddir)/$@ $(srcdir)/../mono.snk + +mod-mono-server2.exe: $(sources) + $(GMCS) -d:NET_2_0 $(MCSFLAGS) $(references2) /out:$@ $(build_sources) + $(SN) -q -R $(builddir)/$@ $(srcdir)/../mono.snk + +install-data-local: + $(GACUTIL1) $(GACUTIL_FLAGS) -i $(builddir)/mod-mono-server.exe +#if NET_2_0 + $(GACUTIL2) $(GACUTIL_FLAGS) -i $(builddir)/mod-mono-server2.exe +#endif + +uninstall-local: + -for i in mod-mono-server mod-mono-server2 ; do \ + $(GACUTIL) $(GACUTIL_FLAGS) -u $$(basename $$i .exe) ; \ + done diff --git a/src/Mono.WebServer.Apache/Mono.WebServer.Apache.sources b/src/Mono.WebServer.Apache/Mono.WebServer.Apache.sources new file mode 100644 index 0000000..d152067 --- /dev/null +++ b/src/Mono.WebServer.Apache/Mono.WebServer.Apache.sources @@ -0,0 +1,8 @@ +ApplicationHost.cs +main.cs +RequestBroker.cs +Request.cs +TCPWebSource.cs +WebSource.cs +Worker.cs +WorkerRequest.cs diff --git a/src/ModMonoRequest.cs b/src/Mono.WebServer.Apache/Request.cs similarity index 100% rename from src/ModMonoRequest.cs rename to src/Mono.WebServer.Apache/Request.cs diff --git a/src/ModMonoRequestBroker.cs b/src/Mono.WebServer.Apache/RequestBroker.cs similarity index 100% rename from src/ModMonoRequestBroker.cs rename to src/Mono.WebServer.Apache/RequestBroker.cs diff --git a/src/ModMonoTCPWebSource.cs b/src/Mono.WebServer.Apache/TCPWebSource.cs similarity index 100% rename from src/ModMonoTCPWebSource.cs rename to src/Mono.WebServer.Apache/TCPWebSource.cs diff --git a/src/ModMonoWebSource.cs b/src/Mono.WebServer.Apache/WebSource.cs similarity index 100% rename from src/ModMonoWebSource.cs rename to src/Mono.WebServer.Apache/WebSource.cs diff --git a/src/ModMonoWorker.cs b/src/Mono.WebServer.Apache/Worker.cs similarity index 100% rename from src/ModMonoWorker.cs rename to src/Mono.WebServer.Apache/Worker.cs diff --git a/src/ModMonoWorkerRequest.cs b/src/Mono.WebServer.Apache/WorkerRequest.cs similarity index 100% rename from src/ModMonoWorkerRequest.cs rename to src/Mono.WebServer.Apache/WorkerRequest.cs diff --git a/src/Mono.WebServer.Apache/main.cs b/src/Mono.WebServer.Apache/main.cs new file mode 100644 index 0000000..65a3fdd --- /dev/null +++ b/src/Mono.WebServer.Apache/main.cs @@ -0,0 +1,376 @@ +// +// Mono.WebServer.Apache/main.cs: Mod_mono Backend for XSP. +// +// Authors: +// Gonzalo Paniagua Javier (gonzalo@ximian.com) +// +// (C) 2002,2003 Ximian, Inc (http://www.ximian.com) +// (C) Copyright 2004-2007 Novell, Inc. (http://www.novell.com) +// +// 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. +// + +using System; +using System.Configuration; +using System.Collections.Specialized; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Reflection; +using System.Web.Hosting; +using Mono.WebServer; + +namespace Mono.WebServer.Apache +{ + public class Server + { + + static void ShowVersion () + { + Assembly assembly = Assembly.GetExecutingAssembly (); + string version = assembly.GetName ().Version.ToString (); + object [] att = assembly.GetCustomAttributes (typeof (AssemblyTitleAttribute), false); + //string title = ((AssemblyTitleAttribute) att [0]).Title; + att = assembly.GetCustomAttributes (typeof (AssemblyCopyrightAttribute), false); + string copyright = ((AssemblyCopyrightAttribute) att [0]).Copyright; + att = assembly.GetCustomAttributes (typeof (AssemblyDescriptionAttribute), false); + string description = ((AssemblyDescriptionAttribute) att [0]).Description; + Console.WriteLine ("{0} {1}\n(c) {2}\n{3}", + Path.GetFileName (assembly.Location), version, copyright, description); + } + + static void ShowHelp () + { + Console.WriteLine ("mod-mono-server.exe is a ASP.NET server used from mod_mono."); + Console.WriteLine ("Usage is:\n"); + Console.WriteLine (" mod-mono-server.exe [...]"); + Console.WriteLine (); + Console.WriteLine (" The arguments --filename and --port are mutually exlusive."); + Console.WriteLine (" --filename file: a unix socket filename to listen on."); + Console.WriteLine (" Default value: /tmp/mod_mono_server"); + Console.WriteLine (" AppSettings key name: MonoUnixSocket"); + Console.WriteLine (); + Console.WriteLine (" --port N: n is the tcp port to listen on."); + Console.WriteLine (" Default value: none"); + Console.WriteLine (" AppSettings key name: MonoServerPort"); + Console.WriteLine (); + Console.WriteLine (" --address addr: addr is the ip address to listen on."); + Console.WriteLine (" Default value: 127.0.0.1"); + Console.WriteLine (" AppSettings key name: MonoServerAddress"); + Console.WriteLine (); + Console.WriteLine (" --root rootdir: the server changes to this directory before"); + Console.WriteLine (" anything else."); + Console.WriteLine (" Default value: current directory."); + Console.WriteLine (" AppSettings key name: MonoServerRootDir"); + Console.WriteLine (); + Console.WriteLine (" --appconfigfile FILENAME: adds application definitions from the XML"); + Console.WriteLine (" configuration file. See sample configuration file that"); + Console.WriteLine (" comes with the server."); + Console.WriteLine (" AppSettings key name: MonoApplicationsConfigFile"); + Console.WriteLine (); + Console.WriteLine (" --appconfigdir DIR: adds application definitions from all XML files"); + Console.WriteLine (" found in the specified directory DIR. Files must have"); + Console.WriteLine (" '.webapp' extension"); + Console.WriteLine (" AppSettings key name: MonoApplicationsConfigDir"); + Console.WriteLine (); + Console.WriteLine (" --applications APPS:"); + Console.WriteLine (" a comma separated list of virtual directory and"); + Console.WriteLine (" real directory for all the applications we want to manage"); + Console.WriteLine (" with this server. The virtual and real dirs. are separated"); + Console.WriteLine (" by a colon. Optionally you may specify virtual host name"); + Console.WriteLine (" and a port."); + Console.WriteLine (); + Console.WriteLine (" [[hostname:]port:]VPath:realpath,..."); + Console.WriteLine (); + Console.WriteLine (" Samples: /:."); + Console.WriteLine (" the virtual / is mapped to the current directory."); + Console.WriteLine (); + Console.WriteLine (" /blog:../myblog"); + Console.WriteLine (" the virtual /blog is mapped to ../myblog"); + Console.WriteLine (); + Console.WriteLine (" myhost.someprovider.net:/blog:../myblog"); + Console.WriteLine (" the virtual /blog at myhost.someprovider.net is mapped to ../myblog"); + Console.WriteLine (); + Console.WriteLine (" /:.,/blog:../myblog"); + Console.WriteLine (" Two applications like the above ones are handled."); + Console.WriteLine (" Default value: /:."); + Console.WriteLine (" AppSettings key name: MonoApplications"); + Console.WriteLine (); + Console.WriteLine (" --terminate: gracefully terminates a running mod-mono-server instance."); + Console.WriteLine (" All other options but --filename or --address and --port"); + Console.WriteLine (" are ignored if this option is provided."); + Console.WriteLine (" --master: this instance will be used to by mod_mono to create ASP.NET"); + Console.WriteLine (" applications on demand. If this option is provided, there is no"); + Console.WriteLine (" need to provide a list of applications to start."); + Console.WriteLine (" --nonstop: don't stop the server by pressing enter. Must be used"); + Console.WriteLine (" when the server has no controlling terminal."); + Console.WriteLine (); + Console.WriteLine (" --version: displays version information and exits."); + Console.WriteLine (" --verbose: prints extra messages. Mainly useful for debugging."); + + Console.WriteLine (); + } + + [Flags] + enum Options { + NonStop = 1, + Verbose = 1 << 1, + Applications = 1 << 2, + AppConfigDir = 1 << 3, + AppConfigFile = 1 << 4, + Root = 1 << 5, + FileName = 1 << 6, + Address = 1 << 7, + Port = 1 << 8, + Terminate = 1 << 9, + Https = 1 << 10, + Master = 1 << 11 + } + + static void CheckAndSetOptions (string name, Options value, ref Options options) + { + if ((options & value) != 0) { + ShowHelp (); + Console.WriteLine (); + Console.WriteLine ("ERROR: Option '{0}' duplicated.", name); + Environment.Exit (1); + } + + options |= value; + if ((options & Options.FileName) != 0 && + ((options & Options.Port) != 0 || (options & Options.Address) != 0)) { + ShowHelp (); + Console.WriteLine (); + Console.WriteLine ("ERROR: --port/--address and --filename are mutually exclusive"); + Environment.Exit (1); + } + } + + static NameValueCollection AppSettings { + get { +#if NET_2_0 + return ConfigurationManager.AppSettings; +#else + return ConfigurationSettings.AppSettings; +#endif + } + } + + public static void CurrentDomain_UnhandledException (object sender, UnhandledExceptionEventArgs e) + { + Exception ex = (Exception)e.ExceptionObject; + + Console.WriteLine ("Handling exception type {0}", ex.GetType ().Name); + Console.WriteLine ("Message is {0}", ex.Message); + Console.WriteLine ("IsTerminating is set to {0}", e.IsTerminating); + } + + public static int Main (string [] args) + { + AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler (CurrentDomain_UnhandledException); + + bool nonstop = false; + bool verbose = false; + Trace.Listeners.Add (new TextWriterTraceListener (Console.Out)); + string apps = AppSettings ["MonoApplications"]; + string appConfigDir = AppSettings ["MonoApplicationsConfigDir"]; + string appConfigFile = AppSettings ["MonoApplicationsConfigFile"]; + string rootDir = AppSettings ["MonoServerRootDir"]; + object oport; + string ip = AppSettings ["MonoServerAddress"]; + bool master = false; + string filename = AppSettings ["MonoUnixSocket"]; + if (ip == "" || ip == null) + ip = "0.0.0.0"; + + oport = AppSettings ["MonoServerPort"]; + if (oport == null) + oport = 8080; + + Options options = 0; + int hash = 0; + for (int i = 0; i < args.Length; i++){ + string a = args [i]; + int idx = (i + 1 < args.Length) ? i + 1 : i; + hash ^= args [idx].GetHashCode () + i; + + switch (a){ + case "--filename": + CheckAndSetOptions (a, Options.FileName, ref options); + filename = args [++i]; + break; + case "--terminate": + CheckAndSetOptions (a, Options.Terminate, ref options); + break; + case "--master": + CheckAndSetOptions (a, Options.Master, ref options); + master = true; + break; + case "--port": + CheckAndSetOptions (a, Options.Port, ref options); + oport = args [++i]; + break; + case "--address": + CheckAndSetOptions (a, Options.Address, ref options); + ip = args [++i]; + break; + case "--root": + CheckAndSetOptions (a, Options.Root, ref options); + rootDir = args [++i]; + break; + case "--applications": + CheckAndSetOptions (a, Options.Applications, ref options); + apps = args [++i]; + break; + case "--appconfigfile": + CheckAndSetOptions (a, Options.AppConfigFile, ref options); + appConfigFile = args [++i]; + break; + case "--appconfigdir": + CheckAndSetOptions (a, Options.AppConfigDir, ref options); + appConfigDir = args [++i]; + break; + case "--nonstop": + nonstop = true; + break; + case "--help": + ShowHelp (); + return 0; + case "--version": + ShowVersion (); + return 0; + case "--verbose": + verbose = true; + break; + default: + Console.WriteLine ("Unknown argument: {0}", a); + ShowHelp (); + return 1; + } + } + + if (hash < 0) + hash = -hash; + + string lockfile; + bool useTCP = ((options & Options.Port) != 0); + if (!useTCP) { + if (filename == null || filename == "") + filename = "/tmp/mod_mono_server"; + + if ((options & Options.Address) != 0) { + ShowHelp (); + Console.WriteLine (); + Console.WriteLine ("ERROR: --address without --port"); + Environment.Exit (1); + } lockfile = Path.Combine (Path.GetTempPath (), Path.GetFileName (filename)); + lockfile = String.Format ("{0}_{1}", lockfile, hash); + } else { + lockfile = Path.Combine (Path.GetTempPath (), "mod_mono_TCP_"); + lockfile = String.Format ("{0}_{1}", lockfile, hash); + } + + IPAddress ipaddr = null; + ushort port; + try { + port = Convert.ToUInt16 (oport); + } catch (Exception) { + Console.WriteLine ("The value given for the listen port is not valid: " + oport); + return 1; + } + + try { + ipaddr = IPAddress.Parse (ip); + } catch (Exception) { + Console.WriteLine ("The value given for the address is not valid: " + ip); + return 1; + } + + if (rootDir != null && rootDir != "") { + try { + Environment.CurrentDirectory = rootDir; + } catch (Exception e) { + Console.WriteLine ("Error: {0}", e.Message); + return 1; + } + } + + rootDir = Directory.GetCurrentDirectory (); + + WebSource webSource; + if (useTCP) { + webSource = new ModMonoTCPWebSource (ipaddr, port, lockfile); + } else { + webSource = new ModMonoWebSource (filename, lockfile); + } + + if ((options & Options.Terminate) != 0) { + if (verbose) + Console.WriteLine ("Shutting down running mod-mono-server..."); + + bool res = ((ModMonoWebSource) webSource).GracefulShutdown (); + if (verbose) + Console.WriteLine (res ? "Done." : "Failed"); + + return (res) ? 0 : 1; + } + ApplicationServer server = new ApplicationServer (webSource); + server.Verbose = verbose; + + Console.WriteLine (Assembly.GetExecutingAssembly ().GetName ().Name); + if (apps != null) + server.AddApplicationsFromCommandLine (apps); + + if (appConfigFile != null) + server.AddApplicationsFromConfigFile (appConfigFile); + + if (appConfigDir != null) + server.AddApplicationsFromConfigDirectory (appConfigDir); + + if (!master && apps == null && appConfigDir == null && appConfigFile == null) + server.AddApplicationsFromCommandLine ("/:."); + if (!useTCP) { + Console.WriteLine ("Listening on: {0}", filename); + } else { + Console.WriteLine ("Listening on port: {0", port); + Console.WriteLine ("Listening on address: {0}", ip); + } + + Console.WriteLine ("Root directory: {0}", rootDir); + + try { + if (server.Start (!nonstop) == false) + return 2; + + if (!nonstop) { + Console.WriteLine ("Hit Return to stop the server."); + Console.ReadLine (); + server.Stop (); + } + } catch (Exception e) { + Console.WriteLine ("Error: {0}", e.Message); + return 1; + } + + return 0; + } + } +} + diff --git a/src/Mono.WebServer.FastCgi/ApplicationHost.cs b/src/Mono.WebServer.FastCgi/ApplicationHost.cs new file mode 100644 index 0000000..fd6bbb0 --- /dev/null +++ b/src/Mono.WebServer.FastCgi/ApplicationHost.cs @@ -0,0 +1,83 @@ +// +// ApplicationHost.cs: Hosts ASP.NET applications in their own AppDomain. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; +using Mono.FastCgi; +using Mono.WebServer; +using System.Web; +using System.IO; +using System.Text; + +namespace Mono.WebServer.FastCgi +{ + public class ApplicationHost : BaseApplicationHost + { + public ApplicationHost () + { + } + + public void ProcessRequest (Responder responder) + { + WorkerRequest worker = new WorkerRequest (responder, + this); + + string path = responder.Path; + if (path [path.Length - 1] != '/' && Directory.Exists ( + worker.MapPath (path))) { + Redirect (worker, path + '/'); + return; + } + + ProcessRequest (worker); + } + + private static string content301 = + "\n" + + "\n301 Moved Permanently\n\n" + + "

Moved Permanently

\n" + + "

The document has moved to http://{0}{1}.

\n" + + "\n"; + + private static void Redirect (MonoWorkerRequest wr, string location) + { + string host = wr.GetKnownRequestHeader (HttpWorkerRequest.HeaderHost); + wr.SendStatus (301, "Moved Permanently"); + wr.SendUnknownResponseHeader ("Connection", "close"); + wr.SendUnknownResponseHeader ("Date", DateTime.Now.ToUniversalTime ().ToString ("r")); + wr.SendUnknownResponseHeader ("Location", String.Format ("http://{0}{1}", host, location)); + Encoding enc = Encoding.ASCII; + wr.SendUnknownResponseHeader ("Content-Type", "text/html; charset=" + enc.WebName); + string content = String.Format (content301, host, location); + byte [] contentBytes = enc.GetBytes (content); + wr.SendUnknownResponseHeader ("Content-Length", contentBytes.Length.ToString ()); + wr.SendResponseFromMemory (contentBytes, contentBytes.Length); + wr.FlushResponse (true); + wr.CloseConnection (); + } + } +} diff --git a/src/Mono.WebServer.FastCgi/AssemblyInfo.cs.in b/src/Mono.WebServer.FastCgi/AssemblyInfo.cs.in new file mode 100644 index 0000000..a770fcf --- /dev/null +++ b/src/Mono.WebServer.FastCgi/AssemblyInfo.cs.in @@ -0,0 +1,9 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: AssemblyVersion("@XSP_VERSION@")] +[assembly: AssemblyTitle("Mono.WebServer.FastCGI")] +[assembly: AssemblyDescription("FastCGI Backend for XSP")] +[assembly: AssemblyCopyright("2007 Brian Nickel")] +[assembly: AssemblyDelaySign(true)] +[assembly: AssemblyKeyFile("@top_srcdir@/src/mono.pub")] diff --git a/src/Mono.WebServer.FastCgi/BeginRequestBody.cs b/src/Mono.WebServer.FastCgi/BeginRequestBody.cs new file mode 100644 index 0000000..80bf9d2 --- /dev/null +++ b/src/Mono.WebServer.FastCgi/BeginRequestBody.cs @@ -0,0 +1,154 @@ +// +// Record.cs: Represents the FastCGI BeginRequestBody structure. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; + +namespace Mono.FastCgi { + /// + /// Specifies the role to use for a request. + /// + public enum Role : ushort + { + /// + /// The request is for the role of "Responder". + /// + Responder = 1, + + /// + /// The request is for the role of "Authorizer". + /// + Authorizer = 2, + + /// + /// The request is for the role of "Filter". + /// + Filter = 3 + } + + /// + /// Specifies flags to use for a request. + /// + [Flags] + public enum BeginRequestFlags : byte + { + /// + /// The request has no flags. + /// + None = 0, + + /// + /// The connection is to be kept alive after the request is + /// complete. + /// + KeepAlive = 1 + } + + /// + /// This struct contains the body data for a BeginRequest record. + /// + public struct BeginRequestBody + { + #region Private Fields + + /// + /// Contains the role of the request. + /// + private Role role; + + /// + /// Contains the flags for the request. + /// + private BeginRequestFlags flags; + + #endregion + + + + #region Constructors + + /// + /// Constructs and initializes a new instance of by reading the body of a + /// specified record. + /// + /// + /// A object containing the body to + /// read. + /// + /// + /// is not of type or does not contain + /// exactly 8 bytes of body data. + /// + public BeginRequestBody (Record record) + { + if (record.Type != RecordType.BeginRequest) + throw new ArgumentException ( + Strings.BeginRequestBody_WrongType, + "record"); + + if (record.BodyLength != 8) + throw new ArgumentException ( + Strings.BeginRequestBody_WrongSize, "record"); + + byte[] body = record.GetBody (); + role = (Role) Record.ReadUInt16 (body, 0); + flags = (BeginRequestFlags) body [2]; + } + + #endregion + + + + #region Public Properties + + /// + /// Gets the role of the current instance. + /// + /// + /// A containing the role of the + /// current instance. + /// + public Role Role { + get {return role;} + } + + /// + /// Gets the flags contained in the current instance. + /// + /// + /// A containing the flags + /// contained in the current instance. + /// + public BeginRequestFlags Flags { + get {return flags;} + } + + #endregion + } +} diff --git a/src/Mono.WebServer.FastCgi/ConfigurationManager.cs b/src/Mono.WebServer.FastCgi/ConfigurationManager.cs new file mode 100644 index 0000000..f48fd33 --- /dev/null +++ b/src/Mono.WebServer.FastCgi/ConfigurationManager.cs @@ -0,0 +1,499 @@ +// +// ConfigurationManager.cs: Generic multi-source configuration manager. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// Robert Jordan +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// +using System; +using System.Xml; +using System.IO; +using System.Reflection; +using System.Globalization; +using System.Collections; +using System.Collections.Specialized; +using System.Text; + +namespace Mono.WebServer +{ + public class ConfigurationManager + { + private int hash; + + private XmlNodeList elems; + + private NameValueCollection cmd_args = + new NameValueCollection (); + + private NameValueCollection xml_args = + new NameValueCollection (); + + private NameValueCollection default_args = + new NameValueCollection (); + + public ConfigurationManager (Assembly asm, string resource) + { + XmlDocument doc = new XmlDocument (); + doc.Load (asm.GetManifestResourceStream (resource)); + ImportSettings (doc, default_args, true, false); + + } + + void ImportSettings (XmlDocument doc, NameValueCollection collection, + bool allowDuplicates, bool insertEmptyValue) + { + elems = doc.GetElementsByTagName ("Setting"); + foreach (XmlElement setting in elems) { + string name = GetXmlValue (setting, "Name"); + string value = GetXmlValue (setting, "Value"); + if (name.Length == 0) + throw AppExcept (except_bad_elem, + name, value); + + if (!allowDuplicates && collection [name] != null) + throw AppExcept (except_xml_duplicate, name); + + if (insertEmptyValue || value.Length > 0) + collection [name] = value; + } + } + + private XmlElement GetSetting (string name) + { + foreach (XmlElement setting in elems) + if (GetXmlValue (setting, "Name") == name) + return setting; + + return null; + } + + private string GetValue (string name, out XmlElement setting) + { + setting = GetSetting (name); + + if (setting == null) + return null; + + string value; + if ((value = cmd_args [name]) != null) return value; + if ((value = xml_args [name]) != null) return value; + + string env_setting = GetXmlValue (setting, "Environment"); + if (env_setting.Length > 0) + if ((value = Environment.GetEnvironmentVariable (env_setting)) != null) + return value; + + string app_setting = GetXmlValue (setting, + "AppSetting"); + + if (app_setting.Length > 0) + if ((value = AppSettings [app_setting]) != null) + return value; + + return default_args [name]; + } + + public bool Contains (string name) + { + XmlElement setting; + return GetValue (name, out setting) != null; + } + + private static string except_unregistered = + "Argument \"{0}\" is unknown."; + + private static string except_uint16 = + "Error in argument \"{0}\". \"{1}\" cannot be converted to an integer."; + + private static string except_bool = + "Error in argument \"{0}\". \"{1}\" should be \"True\" or \"False\"."; + + private static string except_directory = + "Error in argument \"{0}\". \"{1}\" is not a directory or does not exist."; + + private static string except_file = + "Error in argument \"{0}\". \"{1}\" does not exist."; + + private static string except_unknown = + "The Argument \"{0}\" has an invalid type: {1}."; + + public object this [string name] { + get { + XmlElement setting; + string value = GetValue (name, out setting); + + if (setting == null) + throw AppExcept (except_unregistered, + name); + + string type = GetXmlValue (setting, + "Type").ToLower ( + CultureInfo.InvariantCulture); + + if (value == null) + switch (type) { + case "uint16": + return 0; + case "bool": + return false; + default: + return null; + } + + switch (type) { + case "string": + return value; + + case "uint16": + try { + return ushort.Parse (value); + } catch (Exception except) { + throw AppExcept (except, + except_uint16, + name, value); + } + + case "bool": + if (value.ToLower () == "true") + return true; + + if (value.ToLower () == "false") + return false; + + throw AppExcept (except_bool, name, + value); + + case "directory": + DirectoryInfo dir = new DirectoryInfo ( + value); + if (dir.Exists) + return value; + + throw AppExcept (except_directory, name, + value); + + case "file": + FileInfo file = new FileInfo (value); + if (file.Exists) + return value; + + throw AppExcept (except_file, name, + value); + + default: + throw AppExcept (except_unknown, name, + type); + } + } + + set { + cmd_args.Set (name, value.ToString ()); + } + } + + private ApplicationException AppExcept (Exception except, + string message, + params object [] args) + { + return new ApplicationException (string.Format ( + CultureInfo.InvariantCulture, message, args), + except); + } + + private ApplicationException AppExcept (string message, + params object [] args) + { + return new ApplicationException (string.Format ( + CultureInfo.InvariantCulture, message, args)); + } + + public void PrintHelp () + { + int left_margin = 0; + foreach (XmlElement setting in elems) { + string show = GetXmlValue (setting, + "ConsoleVisible").ToLower ( + CultureInfo.InvariantCulture); + + if (show != "true") + continue; + + string type = GetXmlValue (setting, + "Type").ToUpper ( + CultureInfo.InvariantCulture); + + int length = 4 + + GetXmlValue (setting, "Name").Length + + (type == "BOOL" ? 14 : type.Length + 1); + + if (length > left_margin) + left_margin = length; + } + + foreach (XmlElement setting in elems) { + string show = GetXmlValue (setting, + "ConsoleVisible").ToLower ( + CultureInfo.InvariantCulture); + + if (show != "true") + continue; + + string type = GetXmlValue (setting, + "Type").ToUpper ( + CultureInfo.InvariantCulture); + + string name = GetXmlValue (setting, "Name"); + string arg = string.Format ( + CultureInfo.InvariantCulture, + " /{0}{1}", + name, + type == "BOOL" ? "[=True|=False]" : + "=" + type); + + Console.Write (arg); + + ArrayList values = new ArrayList (); + foreach (XmlElement desc in setting.GetElementsByTagName ("Description")) + RenderXml (desc, values, 0, 78 - left_margin); + + string app_setting = GetXmlValue (setting, + "AppSetting"); + + if (app_setting.Length > 0) { + string val = AppSettings [app_setting]; + + if (val == null || val.Length == 0) + val = default_args [name]; + + if (val == null || val.Length == 0) + val = "none"; + + values.Add (" Default Value: " + val); + + values.Add (" AppSettings Key Name: " + + app_setting); + + } + + string env_setting = GetXmlValue (setting, + "Environment"); + + if (env_setting.Length > 0) + values.Add (" Environment Variable Name: " + + env_setting); + + values.Add (string.Empty); + + int start = arg.Length; + foreach (string text in values) { + for (int i = start; i < left_margin; i++) + Console.Write (' '); + start = 0; + Console.WriteLine (text); + } + } + } + + private void RenderXml (XmlElement elem, ArrayList values, int indent, int length) + { + foreach (XmlNode node in elem.ChildNodes) { + XmlElement child = node as XmlElement; + switch (node.LocalName) { + case "para": + RenderXml (child, values, indent, length); + values.Add (string.Empty); + break; + case "block": + RenderXml (child, values, indent + 4, length); + break; + case "example": + RenderXml (child, values, indent + 4, length); + values.Add (string.Empty); + break; + case "code": + case "desc": + RenderXml (child, values, indent, length); + break; + case "#text": + RenderText (node.Value, values, indent, length); + break; + } + } + } + + private void RenderText (string text, ArrayList values, int indent, int length) + { + StringBuilder output = CreateBuilder (indent); + int start = -1; + for (int i = 0; i <= text.Length; i ++) { + bool ws = i == text.Length || char.IsWhiteSpace (text [i]); + + if (ws && start >= 0) { + if (output.Length + i - start > length) { + values.Add (output.ToString ()); + output = CreateBuilder (indent); + } + + output.Append (' '); + output.Append (text.Substring (start, i - start)); + + start = -1; + } else if (!ws && start < 0) { + start = i; + } + } + values.Add (output.ToString ()); + } + + private StringBuilder CreateBuilder (int indent) + { + StringBuilder builder = new StringBuilder (80); + for (int i = 0; i < indent; i ++) + builder.Append (' '); + return builder; + } + + public void LoadCommandLineArgs (string [] args) + { + if (args == null) + throw new ArgumentNullException ("args"); + + for (int i = 0; i < args.Length; i ++) { + // Randomize the hash a bit. + int idx = (i + 1 < args.Length) ? i + 1 : i; + hash ^= args [idx].GetHashCode () + i; + + string arg = args [i]; + int len = PrefixLength (arg); + + if (len > 0) + arg = arg.Substring (len); + else { + Console.WriteLine ( + "Warning: \"{0}\" is not a valid argument. Ignoring.", + args [i]); + continue; + } + + if (cmd_args [arg] != null) + Console.WriteLine ( + "Warning: \"{0}\" has already been set. Overwriting.", + args [i]); + + string [] pair = arg.Split (new char [] {'='}, + 2); + + if (pair.Length == 2) { + cmd_args.Add (pair [0], pair [1]); + continue; + } + + XmlElement setting = GetSetting (arg); + if (setting == null) { + Console.WriteLine ( + "Warning: \"{0}\" is an unknown argument. Ignoring.", + args [i]); + continue; + } + + string type = GetXmlValue (setting, + "Type").ToLower ( + CultureInfo.InvariantCulture); + string value; + + if (type == "bool") + value = (i + 1 < args.Length && + (args [i+1].ToLower () == "true" || + args [i+1].ToLower () == "false")) ? + args [++ i] : "True"; + else if (i + 1 < args.Length) + value = args [++i]; + else { + Console.WriteLine ( + "Warning: \"{0}\" is missing its value. Ignoring.", + args [i]); + continue; + } + + cmd_args [arg] = value; + } + } + + private static readonly string except_bad_elem = + "XML setting \"{0}={1}\" is invalid."; + + private static readonly string except_xml_duplicate = + "XML setting \"{0}\" can only be assigned once."; + + public void LoadXmlConfig (string filename) + { + XmlDocument doc = new XmlDocument (); + doc.Load (filename); + ImportSettings (doc, xml_args, false, true); + } + + private int PrefixLength (string arg) + { + if (arg.StartsWith ("--")) + return 2; + + if (arg.StartsWith ("-")) + return 1; + + if (arg.StartsWith ("/")) + return 1; + + return 0; + } + + public int Hash { + get {return hash < 0 ? -hash : hash;} + } + + private static NameValueCollection AppSettings { + get { + #if NET_2_0 + return System.Configuration.ConfigurationManager.AppSettings; + #else + return System.Configuration.ConfigurationSettings.AppSettings; + #endif + } + } + + private static string GetXmlValue (XmlElement elem, string name) + { + string value = elem.GetAttribute (name); + if (value != null && value.Length != 0) + return value; + + foreach (XmlElement child in elem.GetElementsByTagName (name)) { + value = child.InnerText; + if (value != null && value.Length != 0) + return value; + } + + return string.Empty; + } + } +} diff --git a/src/Mono.WebServer.FastCgi/ConfigurationManager.xml b/src/Mono.WebServer.FastCgi/ConfigurationManager.xml new file mode 100644 index 0000000..9d284c5 --- /dev/null +++ b/src/Mono.WebServer.FastCgi/ConfigurationManager.xml @@ -0,0 +1,258 @@ + + + + + + + + + + Shows this help message and exits. + + + + + + Displays version information and exits. + + + + + + Allows the user to stop the server by if "Enter" + is pressed. This should not be used when the server has + no controlling terminal. + + + + + + Prints extra messages. Mainly useful for + debugging. + + + + + + Specifies a file containing configuration options + identical to those available in he command line. + + + + + + + + Specifies the type of socket to listen on. Valid + values are "pipe", "unix", and "tcp". + + "pipe" indicates to use piped socket + opened by the web server when it spawns the + application. + + "unix" indicates that a standard unix + socket should be opened. The file name can be + specified in the "filename" argument or appended + to this argument with a colon, eg: + + + + unix
+ unix:/tmp/fastcgi-mono-socket +
+
+ + "tcp" indicates that a TCP socket should + be opened. The address and port can be specified + in the "port" and "address" arguments with or + appended to this argument with a colon, eg: + + + + tcp
+ tcp:8081
+ tcp:127.0.0.1:8081
+ tcp:0.0.0.0:8081 +
+
+
+
+ + + + Specifies a unix socket filename to listen on.
+ To use this argument, "socket" must be set to "unix".
+
+
+ + + + Specifies the TCP port number to listen on.
+ To use this argument, "socket" must be set to "tcp".
+
+
+ + + + Specifies the IP address to listen on.
+ To use this argument, "socket" must be set to "tcp".
+
+
+ + + + + + Specifies the root directory the server changes to + before doing performing any operations. + + This value is only used when "appconfigfile", + "appconfigdir", or "applications", is set to provide a + relative base path. + + + + + + Adds application definitions from an XML + configuration file, typically with the ".webapp" + extension. See sample configuration file that + comes with the server. + + + + + + Adds application definitions from all XML files + found in the specified directory DIR. Files must + have the ".webapp" extension. + + + + + + Adds applications from a comma separated list of + virtual and physical directory pairs. The pairs are + separated by colons and optionally include the + virtual host name and port to use: + + + [hostname:[port:]]VPath:realpath,... + + + Samples: + + + /:. + The virtual root directory, "/", is + mapped to the current directory or "root" if + specified. + + + + /blog:../myblog + The virtual /blog is mapped to + ../myblog + + + + myhost.someprovider.net:/blog:../myblog + The virtual /blog at + myhost.someprovider.net is mapped to ../myblog. + This means that other host names, like + "localhost" will not be mapped. + + + + /:.,/blog:../myblog + Two applications like the above ones are + handled. + + + + *:80:/:standard,*:433:/:secure + The server uses different applications on + the unsecure and secure ports. + + + + + + + + + + Specifies the maximum number of concurrent + connections the server should accept. + + + + + + Specifies the maximum number of concurrent + requests the server should accept. + + + + + + Allows multiple requests to be send over a single + connection. + + + + + + + + Specifies what log levels to log. It can be any + of the following values, or multiple if comma + separated: + + Debug
+ Notice
+ Warning
+ Error
+ Standard (Notice,Warning,Error)
+ All (Debug,Standard)
+ + This value is only used when "logfile" or + "printlog" are set. +
+
+ + + + Specifies a file to log events to. + + + + + + Prints log messages to the console. + + +
\ No newline at end of file diff --git a/src/Mono.WebServer.FastCgi/Connection.cs b/src/Mono.WebServer.FastCgi/Connection.cs new file mode 100644 index 0000000..5c845c5 --- /dev/null +++ b/src/Mono.WebServer.FastCgi/Connection.cs @@ -0,0 +1,607 @@ +// +// Connection.cs: Handles a FastCGI connection. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; +#if NET_2_0 +using System.Collections.Generic; +#else +using System.Collections; +#endif + +namespace Mono.FastCgi { + /// + /// This class handles a FastCGI connection by processing records and + /// calling responders. + /// + public class Connection + { + #region Private Fields + + /// + /// Contains a list of the current requests. + /// + #if NET_2_0 + private List requests = new List (); + #else + private ArrayList requests = new ArrayList (); + #endif + + /// + /// Contains the socket to communicate over. + /// + private Socket socket; + + /// + /// Contains the server that created the current instance. + /// + private Server server; + + /// + /// Indicates whether or not to keep the connection alive + /// after a current requests has been completed. + /// + private bool keep_alive; + + /// + /// Indicates whether or not to stop the current connection. + /// + private bool stop; + + /// + /// Contains the lock used to regulate the modifying of the + /// request list. + /// + private object request_lock = new object (); + + /// + /// Contains the lock used to regulate the sending of + /// records. + /// + private object send_lock = new object (); + + /// + /// Contains the buffer used to receive records. + /// + private byte[] receive_buffer; + + /// + /// Contains the buffer used to send records. + /// + private byte[] send_buffer; + + #endregion + + + + #region Constructors + + /// + /// Constructs and initializes a new instance of to handle a specified socket created + /// by a specified server. + /// + /// + /// A object to communicate over. + /// + /// + /// A object containing the server that + /// created the new instance. + /// + public Connection (Socket socket, Server server) + { + this.socket = socket; + this.server = server; + server.AllocateBuffers (out receive_buffer, + out send_buffer); + } + + #endregion + + + + #region Public Properties + + /// + /// Gets the number of active requests being managed by the + /// current instance. + /// + /// + /// A containing the number of active + /// requests being managed by the current instance. + /// + public int RequestCount { + get {return requests.Count;} + } + + /// + /// Gets whether or not the current instance is connected. + /// + /// + /// A indicating whether or not the + /// current instance is connected. + /// + public bool IsConnected { + get {return socket.Connected;} + } + + /// + /// Gets the server used to create the current instance. + /// + /// + /// A object containing the server used + /// to create the current instance. + /// + public Server Server { + get {return server;} + } + + #endregion + + + + #region Public Methods + + /// + /// Receives and responds to records until all requests have + /// been completed. + /// + /// + /// If the last received BeginRequest record is flagged + /// with keep-alive, the connection will be kept alive event + /// after all open requests have been completed. + /// + public void Run () + { + Logger.Write (LogLevel.Notice, + Strings.Connection_BeginningRun); + + do { + Record record; + + try { + record = new Record (socket, + receive_buffer); + } catch (System.Net.Sockets.SocketException) { + StopRun (Strings.Connection_RecordNotReceived); + Stop (); + break; + } + + Request request = GetRequest (record.RequestID); + + switch (record.Type) { + + // Creates a new request. + case RecordType.BeginRequest: + + // If a request with the given ID + // already exists, there's a bug in the + // client. Abort. + if (request != null) { + StopRun (Strings.Connection_RequestAlreadyExists); + break; + } + + // If there are unfinished requests + // and multiplexing is disabled, inform + // the client and don't begin the + // request. + if (!server.MultiplexConnections && + UnfinishedRequests) { + EndRequest (record.RequestID, 0, + ProtocolStatus.CantMultiplexConnections); + break; + } + + // If the maximum number of requests is + // reached, inform the client and don't + // begin the request. + if (!server.CanRequest) { + EndRequest (record.RequestID, 0, + ProtocolStatus.Overloaded); + break; + } + + BeginRequestBody body = new BeginRequestBody + (record); + + // If the role is "Responder", and it is + // supported, create a ResponderRequest. + if (body.Role == Role.Responder && + server.SupportsResponder) + request = new ResponderRequest + (record.RequestID, this); + + // If the request is null, the role is + // not supported. Inform the client and + // don't begin the request. + if (request == null) { + Logger.Write (LogLevel.Warning, + Strings.Connection_RoleNotSupported, + body.Role); + EndRequest (record.RequestID, 0, + ProtocolStatus.UnknownRole); + break; + } + + lock (request_lock) { + requests.Add (request); + } + + keep_alive = (body.Flags & + BeginRequestFlags.KeepAlive) != 0; + + break; + + // Gets server values. + case RecordType.GetValues: + byte [] response_data; + + // Look up the data from the server. + try { + #if NET_2_0 + IDictionary pairs_in = + NameValuePair.FromData ( + record.GetBody ()); + IDictionary pairs_out = + server.GetValues ( + pairs_in.Keys); + #else + IDictionary pairs_in = + NameValuePair.FromData ( + record.GetBody ()); + IDictionary pairs_out = + server.GetValues ( + pairs_in.Keys); + #endif + response_data = NameValuePair.GetData (pairs_out); + } catch { + response_data = new byte [0]; + } + + SendRecord (RecordType.GetValuesResult, + record.RequestID, response_data); + break; + + // Sends params to the request. + case RecordType.Params: + if (request == null) { + StopRun (Strings.Connection_RequestDoesNotExist, + record.RequestID); + break; + } + + request.AddParameterData (record.GetBody ()); + + break; + + // Sends standard input to the request. + case RecordType.StandardInput: + if (request == null) { + StopRun (Strings.Connection_RequestDoesNotExist, + record.RequestID); + } + + request.AddInputData (record); + + break; + + // Sends file data to the request. + case RecordType.Data: + if (request == null) { + StopRun (Strings.Connection_RequestDoesNotExist, + record.RequestID); + } + + request.AddFileData (record); + + break; + + // Aborts a request when the server aborts. + case RecordType.AbortRequest: + if (request != null) + break; + + request.Abort ( + Strings.Connection_AbortRecordReceived); + + break; + + // Informs the client that the record type is + // unknown. + default: + Logger.Write (LogLevel.Warning, + Strings.Connection_UnknownRecordType, + record.Type); + SendRecord (RecordType.UnknownType, + record.RequestID, + new UnknownTypeBody ( + record.Type).GetData ()); + + break; + } + } + while (!stop && (UnfinishedRequests || keep_alive)); + + if (requests.Count == 0) { + socket.Close (); + server.EndConnection (this); + server.ReleaseBuffers (receive_buffer, + send_buffer); + } + + Logger.Write (LogLevel.Notice, + Strings.Connection_EndingRun); + } + + /// + /// Sends a record to the client. + /// + /// + /// A specifying the type of record + /// to send. + /// + /// + /// A containing the ID of the request + /// the record is associated with. + /// + /// + /// A containing the body data for the + /// request. + /// + /// + /// If the socket is not connected, the record will not be + /// sent. + /// + /// + /// is . + /// + public void SendRecord (RecordType type, ushort requestID, + byte [] bodyData) + { + SendRecord (type, requestID, bodyData, 0, -1); + } + + /// + /// Sends a record to the client. + /// + /// + /// A specifying the type of record + /// to send. + /// + /// + /// A containing the ID of the request + /// the record is associated with. + /// + /// + /// A containing the body data for the + /// request. + /// + /// + /// A specifying the index in at which the body begins. + /// + /// + /// A specifying the length of the body in + /// or -1 if all remaining data + /// (.Length - ) is used. + /// + /// + /// If the socket is not connected, the record will not be + /// sent. + /// + /// + /// is . + /// + /// + /// is outside of the range + /// of . + /// + /// + /// contains more than 65535 + /// bytes or is set to -1 and calculated to be greater than + /// 65535 bytes. + /// + public void SendRecord (RecordType type, ushort requestID, + byte [] bodyData, int bodyIndex, + int bodyLength) + { + if (IsConnected) + lock (send_lock) { + try { + new Record (1, type, requestID, + bodyData, bodyIndex, + bodyLength).Send ( + socket, + send_buffer); + } catch (System.Net.Sockets.SocketException) { + } + } + } + + /// + /// Sends an EndRequest record with a specified request ID, + /// application status, and protocol status, and releases the + /// associated resources. + /// + /// + /// A containing the ID of the request + /// to end. + /// + /// + /// A containing the application + /// status the request ended with. + /// This is the same value as would be returned by a + /// program on termination. On successful termination, this + /// would be zero. + /// + /// + /// A containing the FastCGI + /// protocol status with which the request is being ended. + /// + public void EndRequest (ushort requestID, int appStatus, + ProtocolStatus protocolStatus) + { + EndRequestBody body = new EndRequestBody (appStatus, + protocolStatus); + + if (IsConnected) + new Record (1, RecordType.EndRequest, requestID, + body.GetData ()).Send (socket); + + int index = GetRequestIndex (requestID); + + if (index >= 0) { + lock (request_lock) { + requests.RemoveAt (index); + } + } + + if (requests.Count == 0 && (!keep_alive || stop)) { + socket.Close (); + server.EndConnection (this); + server.ReleaseBuffers (receive_buffer, + send_buffer); + } + } + + /// + /// Stops the current instance by ending all the open + /// requests and closing the socket. + /// + public void Stop () + { + stop = true; + + #if NET_2_0 + foreach (Request req in new List (requests)) + #else + foreach (Request req in new ArrayList (requests)) + #endif + EndRequest (req.RequestID, -1, + ProtocolStatus.RequestComplete); + } + + #endregion + + + + #region Private Properties + + /// + /// Gets whether or not more request data is expected by the + /// current instance. + /// + /// + /// A indicating whether or not more + /// request data is expected by the current instance. + /// + /// + /// If , more data is expected from a + /// open request. For instance, the input data has not been + /// completed. + /// + private bool UnfinishedRequests { + get { + foreach (Request request in requests) + if (request.DataNeeded) + return true; + + return false; + } + } + + #endregion + + + + #region Private Methods + + /// + /// Gets the request in the current instance with a specified + /// request ID. + /// + /// + /// A containing the ID of the request + /// to get. + /// + /// + /// A object containing the request + /// with the with the specified request ID, or if it was not found. + /// + private Request GetRequest (ushort requestID) + { + foreach (Request request in requests) + if (request.RequestID == requestID) + return request; + + return null; + } + + /// + /// Gets the index of the request in + /// with a specified request ID. + /// + /// + /// A containing the ID of the request + /// to get. + /// + /// + /// A containing the index of the request + /// in with the with the specified + /// request ID, or -1 if it was not found. + /// + private int GetRequestIndex (ushort requestID) + { + int i = 0; + int count = requests.Count; + while (i < count && + (requests [i] as Request).RequestID != requestID) + i ++; + + return (i != count) ? i : -1; + } + + /// + /// Flags the process to stop and writes an error message to + /// the log. + /// + private void StopRun (string message, params object [] args) + { + Logger.Write (LogLevel.Error, message, args); + Logger.Write (LogLevel.Error, + Strings.Connection_Terminating); + + Stop (); + } + + #endregion + } +} diff --git a/src/Mono.WebServer.FastCgi/EndRequestBody.cs b/src/Mono.WebServer.FastCgi/EndRequestBody.cs new file mode 100644 index 0000000..c8aef03 --- /dev/null +++ b/src/Mono.WebServer.FastCgi/EndRequestBody.cs @@ -0,0 +1,137 @@ +// +// Record.cs: Represents the FastCGI EndRequestBody structure. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; + +namespace Mono.FastCgi { + /// + /// Specifies the protocol status at the end of a request. + /// + public enum ProtocolStatus : byte + { + /// + /// The request was completed successfully. + /// + RequestComplete = 0, + + /// + /// The request cannot be complete because it would require + /// multiplexing. + /// + CantMultiplexConnections = 1, + + /// + /// The request cannot be completed becuase a resource is + /// overloaded. + /// + Overloaded = 2, + + /// + /// The request cannot be completed becuase the role is + /// unknown. + /// + UnknownRole = 3 + } + + /// + /// This struct contains the body data for an EndRequest record. + /// + public struct EndRequestBody + { + #region Private Fields + + /// + /// Contains the application status. + /// + int app_status; + + /// + /// Contains the protocol status. + /// + ProtocolStatus protocol_status; + + #endregion + + + + #region Constructors + + /// + /// Constructs and initializes a new instance of with a specified application + /// status and protocol status. + /// + /// + /// A containing the application + /// status the request ended with. + /// This is the same value as would be returned by a + /// program on termination. On successful termination, this + /// would be zero. + /// + /// + /// A containing the FastCGI + /// protocol status with which the request is being ended. + /// + public EndRequestBody (int appStatus, + ProtocolStatus protocolStatus) + { + app_status = appStatus; + protocol_status = protocolStatus; + } + + #endregion + + + + #region Public Methods + + /// + /// Gets the data contained in the current instance. + /// + /// + /// A containing the data contained in + /// the current instance. + /// + public byte [] GetData () + { + uint app; + unchecked { + app = (uint) app_status; + } + byte [] data = new byte [8]; + data [0] = (byte)((app >> 24) & 0xFF); + data [1] = (byte)((app >> 16) & 0xFF); + data [2] = (byte)((app >> 8) & 0xFF); + data [3] = (byte)((app ) & 0xFF); + data [4] = (byte) protocol_status; + return data; + } + + #endregion + } +} diff --git a/src/Mono.WebServer.FastCgi/ISocketAbstraction.cs b/src/Mono.WebServer.FastCgi/ISocketAbstraction.cs new file mode 100644 index 0000000..5f52e68 --- /dev/null +++ b/src/Mono.WebServer.FastCgi/ISocketAbstraction.cs @@ -0,0 +1,59 @@ +// +// SocketAbstractions/Socket.cs: Abstracts socket operations. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; + +namespace Mono.FastCgi { + /// + /// This abstract class provides a wrapper around socket methods and + /// is to be removed once a FILDES solution has been reached. + /// + public abstract class Socket + { + /// + public abstract void Close (); + + /// + public abstract int Receive (byte [] buffer, int offset, int size, System.Net.Sockets.SocketFlags flags); + + /// + public abstract int Send (byte [] data, int offset, int size, System.Net.Sockets.SocketFlags flags); + + /// + public abstract void Listen (int backlog); + + /// + public abstract IAsyncResult BeginAccept (AsyncCallback callback, object state); + + /// + public abstract Socket EndAccept (IAsyncResult asyncResult); + + /// + public abstract bool Connected {get;} + } +} \ No newline at end of file diff --git a/src/Mono.WebServer.FastCgi/Logger.cs b/src/Mono.WebServer.FastCgi/Logger.cs new file mode 100644 index 0000000..47ecee7 --- /dev/null +++ b/src/Mono.WebServer.FastCgi/Logger.cs @@ -0,0 +1,321 @@ +// +// Logger.cs: Logs server events. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; +using System.IO; +using System.Text; +using System.Globalization; + +namespace Mono.FastCgi { + /// + /// Specifies what type of message to log. + /// + [Flags] + public enum LogLevel + { + /// + /// No messages will be logged. + /// + None = 0x00, + + /// + /// Error messages will be logged. + /// + Error = 0x01, + + /// + /// Warning message will be logged. + /// + Warning = 0x02, + + /// + /// Notice messages will be logged. + /// + Notice = 0x04, + + /// + /// Debug messages will be logged. + /// + Debug = 0x08, + + /// + /// Standard messages will be logged. + /// + Standard = Error | Warning | Notice, + + /// + /// All messages will be logged. + /// + All = Error | Warning | Notice | Debug + } + + + + /// + /// This class stores log messages in a specified file. + /// + public class Logger + { + #region Private Fields + + /// + /// Contains the writer to ouput to. + /// + private StreamWriter writer; + + /// + /// Indicates whether or not to write to the console. + /// + private bool write_to_console; + + /// + /// Contains the bitwise combined log levels to write. + /// + private LogLevel level = LogLevel.Standard; + + /// + /// Contains the lock object to use on the writer. + /// + private object write_lock = new object (); + + /// + /// Contains the singleton instance of the writer. + /// + private static Logger logger = new Logger (); + + #endregion + + + + #region Private Methods + + /// + /// Finalizes the singleton instance by closing the stream. + /// + ~Logger () + { + Close (); + } + + #endregion + + + + #region Public Static Properties + + /// + /// Gets and sets the levels of messages to log. + /// + /// + /// A bitwise combined specifying the + /// levels of events to log. + /// + public static LogLevel Level { + get {return logger.level;} + set {logger.level = value;} + } + + /// + /// Gets and sets whether or not to write log messages to the + /// console. + /// + /// + /// A indicating whether or not log + /// messages will be displayed in the console. + /// + public static bool WriteToConsole { + get {return logger.write_to_console;} + set {logger.write_to_console = value;} + } + + #endregion + + + + #region Public Static Methods + + /// + /// Opens a file to log to. + /// + /// + /// A containing the path of the + /// file to open. + /// This value is the same as the parameter that would + /// be passed to . + /// + /// + /// For information on what exceptions are thrown by this + /// method, see . + /// + public static void Open (string path) + { + if (path == null) + throw new ArgumentNullException ("path"); + + lock (logger.write_lock) { + Close (); + Stream stream = File.Open (path, + FileMode.Append, FileAccess.Write, + FileShare.ReadWrite); + stream.Seek (0, SeekOrigin.End); + logger.writer = new StreamWriter (stream); + } + } + + /// + /// Writes a formatted string with a specified warning level + /// to the log file, if one exists. + /// + /// + /// A containing the severity of the + /// message. + /// + /// + /// A object to use when + /// formatting values for the message. + /// + /// + /// A containing the format to use for + /// the message. + /// + /// + /// A containing values to insert + /// into the format. + /// + /// + /// The message will only be written to the log if + /// contains . + /// + /// See + /// for more details on this method's arguments. + /// + public static void Write (LogLevel level, + IFormatProvider provider, + string format, params object [] args) + { + Write (level, string.Format (provider, format, args)); + } + + /// + /// Writes a formatted string with a specified warning level + /// to the log file, if one exists. + /// + /// + /// A containing the severity of the + /// message. + /// + /// + /// A containing the format to use for + /// the message. + /// + /// + /// A containing values to insert + /// into the format. + /// + /// + /// The message will only be written to the log if + /// contains . + /// + /// This method outputs using the current culture of + /// the assembly. To use a different culture, use + /// . + /// See + /// for more details on this method's arguments. + /// + public static void Write (LogLevel level, string format, + params object [] args) + { + Write (level, CultureInfo.CurrentCulture, format, args); + } + + /// + /// Writes a formatted string with a specified warning level + /// to the log file, if one exists. + /// + /// + /// A containing the severity of the + /// message. + /// + /// + /// A containing the message to write. + /// + /// + /// The message will only be written to the log if + /// contains . + /// + /// + public static void Write (LogLevel level, string message) + { + if (logger.writer == null && !logger.write_to_console) + return; + + if ((Level & level) == LogLevel.None) + return; + + string text = string.Format (CultureInfo.CurrentCulture, + Strings.Logger_Format, + DateTime.Now, + level, + message); + + lock (logger.write_lock) { + if (logger.write_to_console) + Console.WriteLine (text); + + if (logger.writer != null) { + logger.writer.WriteLine (text); + logger.writer.Flush (); + } + } + } + + /// + /// Closes the log file and flushes its output. + /// + /// + /// This method is called automatically when the class is + /// destroyed. + /// + public static void Close () + { + lock (logger.write_lock) { + if (logger.writer == null) + return; + + logger.writer.Flush (); + logger.writer.Close (); + logger.writer = null; + } + } + + #endregion + } +} diff --git a/src/Mono.WebServer.FastCgi/Makefile.am b/src/Mono.WebServer.FastCgi/Makefile.am new file mode 100644 index 0000000..236a59e --- /dev/null +++ b/src/Mono.WebServer.FastCgi/Makefile.am @@ -0,0 +1,48 @@ +builddir=$(top_builddir)/src/Mono.WebServer.FastCgi + +MCSFLAGS= -debug+ -debug:full -nologo -nowarn:618 $(WEBTRACING) -unsafe+ + +GACUTIL1=$(GACUTIL) -package 1.0 + +if NET_2_0 +XSP2_EXE = fastcgi-mono-server2.exe +GACUTIL2=$(GACUTIL) -package 2.0 +endif + +noinst_SCRIPTS = fastcgi-mono-server.exe $(XSP2_EXE) + +CLEANFILES = *.exe *.mdb + +# +references = -r:System.Web.dll -r:../Mono.WebServer/Mono.WebServer.dll -r:Mono.Security.dll -r:Mono.Posix.dll +references2 = -r:System.Web.dll -r:System.Configuration.dll -r:../Mono.WebServer/Mono.WebServer2.dll -r:Mono.Security.dll -r:Mono.Posix.dll + +sources = $(shell cat $(srcdir)/Mono.WebServer.FastCgi.sources) +build_sources = $(addprefix $(srcdir)/, $(sources)) AssemblyInfo.cs + +resources = ConfigurationManager.xml +build_resources = $(addprefix $(srcdir)/, $(resources)) + +EXTRA_DIST = $(sources) AssemblyInfo.cs.in $(resources) Mono.WebServer.FastCgi.sources + +fastcgi-mono-server.exe: $(build_sources) $(build_resources) + $(MCS) $(MCSFLAGS) $(references) /out:$@ \ + $(build_resources:%=-resource:%) $(build_sources) + $(SN) -q -R $(builddir)/$@ $(srcdir)/../mono.snk + +fastcgi-mono-server2.exe: $(build_sources) $(build_resources) + $(GMCS) -d:NET_2_0 $(MCSFLAGS) $(references2) /out:$@ \ + $(build_resources:%=-resource:%) $(build_sources) + $(SN) -q -R $(builddir)/$@ $(srcdir)/../mono.snk + +install-data-local: + $(GACUTIL1) $(GACUTIL_FLAGS) -i $(builddir)/fastcgi-mono-server.exe + +#if NET_2_0 + $(GACUTIL2) $(GACUTIL_FLAGS) -i $(builddir)/fastcgi-mono-server2.exe +#endif + +uninstall-local: + -for i in fastcgi-mono-server fastcgi-mono-server2 ; do \ + $(GACUTIL) $(GACUTIL_FLAGS) -u $$(basename $$i .exe) ; \ + done diff --git a/src/Mono.WebServer.FastCgi/Mono.WebServer.FastCgi.sources b/src/Mono.WebServer.FastCgi/Mono.WebServer.FastCgi.sources new file mode 100644 index 0000000..6a04741 --- /dev/null +++ b/src/Mono.WebServer.FastCgi/Mono.WebServer.FastCgi.sources @@ -0,0 +1,23 @@ +ApplicationHost.cs +BeginRequestBody.cs +ConfigurationManager.cs +Connection.cs +EndRequestBody.cs +ISocketAbstraction.cs +Logger.cs +main.cs +NameValuePair.cs +Record.cs +Request.cs +Responder.cs +ResponderRequest.cs +Server.cs +SocketFactory.cs +StandardSocket.cs +Strings.cs +TcpSocket.cs +UnixSocket.cs +UnknownTypeBody.cs +UnmanagedSocket.cs +WebSource.cs +WorkerRequest.cs diff --git a/src/Mono.WebServer.FastCgi/NameValuePair.cs b/src/Mono.WebServer.FastCgi/NameValuePair.cs new file mode 100644 index 0000000..30f149f --- /dev/null +++ b/src/Mono.WebServer.FastCgi/NameValuePair.cs @@ -0,0 +1,484 @@ +// +// NameValuePair.cs: Handles the parsing of FastCGI name/value pairs. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; +using System.Text; +#if NET_2_0 +using System.Collections.Generic; +#else +using System.Collections; +#endif + +namespace Mono.FastCgi { + /// + /// This struct reads and writes FastCGI name/value pairs. + /// + public struct NameValuePair + { + #region Private Fields + + /// + /// Contains the name of the pair. + /// + private string name; + + /// + /// Contains the value of the pair. + /// + private string value; + + /// + /// Contains the encoding to use when reading and writing + /// params. + /// + private static Encoding encoding = Encoding.Default; + + #endregion + + + + #region Public Fields + + /// + /// A contstant representation of an empty . + /// + /// + /// An empty . + /// + public static readonly NameValuePair Empty = new NameValuePair ( + null, null); + + #endregion + + + + #region Constructors + + /// + /// Constructs and initializes a new instance of with a specified name and value. + /// + /// + /// A containing the name for the + /// new instance. + /// + /// + /// A containing the value for the + /// new instance. + /// + public NameValuePair (string name, string value) + { + this.name = name; + this.value = value; + } + + /// + /// Constructs and initializes a new instance of reading it from specified data + /// at a specified index, moving the index to the position of + /// the next pair. + /// + /// + /// A containing the name/value pair to + /// read. + /// + /// + /// A specifying the index at which to + /// read. + /// + /// + /// is . + /// + /// + /// The data cannot be read at the + /// because it would read outside of the array. + /// + public NameValuePair (byte [] data, ref int index) + { + if (data == null) + throw new ArgumentNullException ("data"); + + // Name/value pairs are stored with their lengths first, + // then their contents. + + // Lengths are stored in 1 or 4 bytes depending on the + // size of the contents. + int name_length = ReadLength (data, ref index); + int value_length = ReadLength (data, ref index); + + // Do a sanity check on the size of the data. + if (index + name_length + value_length > data.Length) + throw new ArgumentOutOfRangeException ("index"); + + // Make sure the encoding doesn't change while running. + Encoding enc = encoding; + + // Read the name. + this.name = enc.GetString (data, index, name_length); + index += name_length; + + // Read the value. + this.value = enc.GetString (data, index, value_length); + index += value_length; + + Logger.Write (LogLevel.Debug, + Strings.NameValuePair_ParameterRead, + this.name, this.value); + } + + #endregion + + + + #region Public Properties + + /// + /// Gets the name of the current instance. + /// + /// + /// A containing the name of the + /// current instance. + /// + public string Name { + get {return name;} + } + + /// + /// Gets the value of the current instance. + /// + /// + /// A containing the value of the + /// current instance. + /// + public string Value { + get {return value;} + } + + #endregion + + + + #region Public Static Properties + + /// + /// Gets and sets the encoding to use when reading and + /// writing instances of to + /// memory. + /// + /// + /// A to use reading and writing + /// to memory. + /// + public static Encoding Encoding { + get {return encoding;} + set {encoding = value != null ? value : Encoding.Default;} + } + + #endregion + + + + #region Public Static Methods + + /// + /// Reads FastCGI name/value pairs from memory and stores + /// them as a + #if NET_2_0 + /// . + #else + /// . + #endif + /// + /// + /// A containing a collection of + /// FastCGI name/value pairs. + /// + /// + #if NET_2_0 + /// A + #else + /// A + #endif + /// object containing the name/value pairs read from + /// . + /// + /// + /// is . + /// + #if NET_2_0 + public static IDictionary FromData (byte [] data) + #else + public static IDictionary FromData (byte [] data) + #endif + { + if (data == null) + throw new ArgumentNullException ("data"); + + // Specialized.NameValueCollection would probably be + // better, but it doesn't implement IDictionary. + #if NET_2_0 + Dictionary pairs = + new Dictionary (); + #else + Hashtable pairs = new Hashtable (); + #endif + int index = 0; + + // Loop through the array, reading pairs at a specified + // position until the end is reached. + + while (index < data.Length) { + NameValuePair pair = new NameValuePair + (data, ref index); + + if (pairs.ContainsKey (pair.Name)) { + Logger.Write (LogLevel.Warning, + Strings.NameValuePair_DuplicateParameter, + pair.Name); + + pairs [pair.Name] = pair.Value; + } else + pairs.Add (pair.Name, pair.Value); + } + + return pairs; + } + + /// + /// Reads name/value pairs from a + #if NET_2_0 + /// + #else + /// + #endif + /// and stores them as FastCGI name/value pairs. + /// + /// + #if NET_2_0 + /// A + #else + /// A + #endif + /// containing string pairs. + /// + /// + /// A containing FastCGI name/value + /// pairs. + /// + /// + /// is . + /// + /// + /// contains a name or value not of + /// type . + /// + #if NET_2_0 + public static byte [] GetData (IDictionary pairs) + #else + public static byte [] GetData (IDictionary pairs) + #endif + { + if (pairs == null) + throw new ArgumentNullException ("pairs"); + + // Make sure the encoding doesn't change while running. + Encoding enc = encoding; + + // Get the total size of the new array and validate the + // contents of "pairs". + + int total_size = 0; + + #if NET_2_0 + foreach (string key in pairs.Keys) + #else + foreach (object key in pairs.Keys) + #endif + { + string name = key as string; + string value = pairs [key] as string; + + // Sanity check: "pairs" must only contain + // strings. + if (name == null || value == null) + throw new ArgumentException ( + Strings.NameValuePair_DictionaryContainsNonString, + "pairs"); + + int name_length = enc.GetByteCount (name); + int value_length = enc.GetByteCount (value); + + total_size += name_length > 0x7F ? 4 : 1; + total_size += value_length > 0x7F ? 4 : 1; + total_size += name_length + value_length; + } + + byte [] data = new byte [total_size]; + + // Fill the data array with the data. + int index = 0; + + #if NET_2_0 + foreach (string key in pairs.Keys) + #else + foreach (object key in pairs.Keys) + #endif + { + string name = key as string; + string value = pairs [key] as string; + + int name_length = enc.GetByteCount (name); + int value_length = enc.GetByteCount (value); + + WriteLength (data, ref index, name_length); + WriteLength (data, ref index, value_length); + index += enc.GetBytes (name, 0, name.Length, data, index); + index += enc.GetBytes (value, 0, value.Length, data, index); + } + + return data; + } + + #endregion + + + + #region Private Static Methods + + /// + /// Reads a FastCGI name/value length from specified data at + /// a specified position, moving the index to the position + /// after the length data. + /// + /// + /// A containing a FastCGI name/value + /// length. + /// + /// + /// A specifying at what index to read. + /// + /// + /// A containing the length. + /// + /// + /// For values less than 128, lengths are stored as a single + /// byte. Otherwise, they are stored as four bytes, with a + /// maximum value of . + /// + /// + /// is less than zero or would + /// require reading past the end of . + /// + private static int ReadLength (byte [] data, ref int index) + { + if (index < 0) + throw new ArgumentOutOfRangeException ("index"); + + if (index >= data.Length) + throw new ArgumentOutOfRangeException ("index"); + + // Lengths are stored in either 1 or 4 bytes. For + // lengths under 128 bytes, which are the most common, a + // single byte is used. + + if (data [index] < 0x80) + return data [index++]; + + // If the MSB for the size byte is set (value >= 0x80), + // a 4 byte value is used. However, the MSB in the first + // byte is not included, as it was used as an indicator. + + if (index > data.Length - 4) + throw new ArgumentOutOfRangeException ("index"); + + + return (0x7F & (int) data [index++]) * 0x1000000 + + ((int) data [index++]) * 0x10000 + + ((int) data [index++]) *0x100 + + ((int) data [index++]); + + // TODO: Returns zero. What gives? + //return (0x7F & (int) data [index++]) << 24 + // + ((int) data [index++]) << 16 + // + ((int) data [index++]) << 8 + // + ((int) data [index++]); + } + + /// + /// Writes a FastCGI name/value length to specified data at + /// a specified position, moving the index to the position + /// after the length data. + /// + /// + /// A to write to. + /// + /// + /// A specifying at what index to write. + /// + /// + /// A containing the length. + /// + /// + /// For values less than 128, lengths are stored as a single + /// byte. Otherwise, they are stored as four bytes, with a + /// maximum value of . + /// + /// + /// represents a negative length. + /// + /// + /// is less than zero or would + /// require writing past the end of . + /// + private static void WriteLength (byte [] data, ref int index, + int length) + { + if (length < 0) + throw new ArgumentException ( + Strings.NameValuePair_LengthLessThanZero, + "length"); + + if (index < 0 || + index > data.Length - (length < 0x80 ? 1 : 4)) + throw new ArgumentOutOfRangeException ("index"); + + if (length < 0x80) { + data [index++] = (byte) length; + return; + } + + data [index++] = (byte) ((length & 0x7F000000) >> 24); + data [index++] = (byte) ((length & 0xFF0000) >> 16); + data [index++] = (byte) ((length & 0xFF00) >> 8); + data [index++] = (byte) (length & 0xFF); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Mono.WebServer.FastCgi/Record.cs b/src/Mono.WebServer.FastCgi/Record.cs new file mode 100644 index 0000000..1867a79 --- /dev/null +++ b/src/Mono.WebServer.FastCgi/Record.cs @@ -0,0 +1,621 @@ +// +// Record.cs: Handles sending and receiving FastCGI records via sockets. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; +using System.Globalization; + +namespace Mono.FastCgi { + /// + /// Specifies the type of information contained in the record. + /// + public enum RecordType : byte { + /// + /// No record type specified. + /// + None = 0, + + /// + /// The record contains the beginning of a request. Sent by + /// the client.) + /// + BeginRequest = 1, + + /// + /// The record is informing that a request has been aborted. + /// (Sent by the client.) + /// + AbortRequest = 2, + + /// + /// The record contains the end of a request. (Sent by the + /// server.) + /// + EndRequest = 3, + + /// + /// The record contains the parameters for a request. (Sent + /// by the client.) + /// + Params = 4, + + /// + /// The record contains standard input for a request. (Sent + /// by the client.) + /// + StandardInput = 5, + + /// + /// The record contains standard output for a request. (Sent + /// by the server.) + /// + StandardOutput = 6, + + /// + /// The record contains standard error for a request. (Sent + /// by the server.) + /// + StandardError = 7, + + /// + /// The record contains file contents for a request. (Sent + /// by the client.) + /// + Data = 8, + + /// + /// The record contains a request for server values. (Sent + /// by the client.) + /// + GetValues = 9, + + /// + /// The record contains a server values. (Sent by the + /// server.) + /// + GetValuesResult = 10, + + /// + /// The record contains a notice of failure to recognize a + /// record type. (Sent by the server.) + /// + UnknownType = 11 + } + + /// + /// This struct sends and receives FastCGI records. + /// + public struct Record + { + #region Private Fields + + /// + /// Contains the FastCGI version. + /// + private byte version; + + /// + /// Contains the record type. + /// + private RecordType type; + + /// + /// Contains the request ID. + /// + private ushort request_id; + + /// + /// Contains the record data plus padding. + /// + /// + /// The actual body is stored starting at with a length of bytes of this property. + /// + private byte [] data; + + /// + /// Contains the index at which the body begins. + /// + private int body_index; + + /// + /// Contains the length of the body. + /// + private ushort body_length; + + /// + /// Contains the suggested buffer size, equal to the maximum + /// possible size of a record. + /// + public const int SuggestedBufferSize = 0x08 + 0xFFFF + 0xFF; + + #endregion + + + + #region Public Fields + + /// + /// The size of a FastCGI record header. + /// + public const int HeaderSize = 8; + + #endregion + + + + #region Constructors + + /// + /// Constructs and initializes a new instance of by reading the contents from a specified + /// socket. + /// + /// + /// A object to receive the record data + /// from. + /// + /// + /// To improve performance, consider using a buffer and + /// instead. + /// + /// + /// is . + /// + /// + /// does not contain a complete + /// record. + /// + public Record (Socket socket) : this (socket, null) + { + } + + /// + /// Constructs and initializes a new instance of by reading the contents from a specified + /// socket. + /// + /// + /// A object to receive the record data + /// from. + /// + /// + /// A containing the buffer to use when + /// receiving from the socket or to + /// create the buffers on the fly. + /// + /// + /// If is not , the suggested size is . If the size of the buffer + /// is insufficient to read the data, a sufficiently sized + /// array will be created on a per-instance basis. + /// + /// If a buffer is used, the new instance + /// is only valid until the same buffer is used again. + /// Therefore, use an extra degree of caution when using + /// this constructor. + /// + /// + /// + /// is . + /// + /// + /// does not contain a complete + /// record. + /// + public Record (Socket socket, byte [] buffer) + { + if (socket == null) + throw new ArgumentNullException ("socket"); + + byte[] header_buffer = (buffer != null && buffer.Length + > 8) ? buffer : new byte [HeaderSize]; + byte padding_length; + + // Read the 8 byte record header. + ReceiveAll (socket, header_buffer, HeaderSize); + + // Read the values from the data. + version = header_buffer [0]; + type = (RecordType) header_buffer [1]; + request_id = ReadUInt16 (header_buffer, 2); + body_length = ReadUInt16 (header_buffer, 4); + padding_length = header_buffer [6]; + + int total_length = body_length + padding_length; + + data = (buffer != null && buffer.Length >= total_length) + ? buffer : new byte [total_length]; + body_index = 0; + + // Read the record data, and throw an exception if the + // complete data cannot be read. + if (total_length > 0) + ReceiveAll (socket, data, total_length); + + Logger.Write (LogLevel.Debug, + Strings.Record_Received, + Type, RequestID, BodyLength); + } + + /// + /// Constructs and initializes a new instance of populating it with a specified version, + /// type, ID, and body. + /// + /// + /// A containing the FastCGI version the + /// record is structured for. + /// + /// + /// A containing the type of + /// record to create. + /// + /// + /// A containing the ID of the request + /// associated with the new record. + /// + /// + /// A containing the contents to use + /// in the new record. + /// + /// + /// + /// The new instance will store a reference to and as such be invalid when the + /// value changes externally. + /// + /// + /// + /// is . + /// + /// + /// contains more than 65535 + /// bytes and cannot be sent. + /// + public Record (byte version, RecordType type, ushort requestID, + byte [] bodyData) : this (version, type, + requestID, bodyData, + 0, -1) + { + } + + /// + /// Constructs and initializes a new instance of populating it with a specified version, + /// type, ID, and body. + /// + /// + /// A containing the FastCGI version the + /// record is structured for. + /// + /// + /// A containing the type of + /// record to create. + /// + /// + /// A containing the ID of the request + /// associated with the new record. + /// + /// + /// A containing the contents to use + /// in the new record. + /// + /// + /// A specifying the index in at which the body begins. + /// + /// + /// A specifying the length of the body in + /// or -1 if all remaining data + /// (.Length - ) is used. + /// + /// + /// + /// The new instance will store a reference to and as such be invalid when the + /// value changes externally. + /// + /// + /// + /// is . + /// + /// + /// is outside of the range + /// of . + /// + /// + /// contains more than 65535 + /// bytes or is set to -1 and calculated to be greater than + /// 65535 bytes. + /// + public Record (byte version, RecordType type, ushort requestID, + byte [] bodyData, int bodyIndex, int bodyLength) + { + if (bodyData == null) + throw new ArgumentNullException ("bodyData"); + + if (bodyIndex < 0 || bodyIndex > bodyData.Length) + throw new ArgumentOutOfRangeException ( + "bodyIndex"); + + if (bodyLength < 0) + bodyLength = bodyData.Length - bodyIndex; + + if (bodyLength > 0xFFFF) + throw new ArgumentException ( + Strings.Record_DataTooBig, + "data"); + + + this.version = version; + this.type = type; + this.request_id = requestID; + this.data = bodyData; + this.body_index = bodyIndex; + this.body_length = (ushort) bodyLength; + } + + #endregion + + + + #region Public Properties + + /// + /// Gets the FastCGI version of the current instance. + /// + /// + /// A containing the FastCGI version of + /// the current instance. + /// + public byte Version { + get {return version;} + } + + /// + /// Gets the FastCGI record type of the current instance. + /// + /// + /// A containing the FastCGI record type + /// of the current instance. + /// + public RecordType Type { + get {return type;} + } + + /// + /// Gets the ID of the request associated with the current + /// instance. + /// + /// + /// A containing the ID of the request + /// associated with the current instance. + /// + public ushort RequestID { + get {return request_id;} + } + + /// + /// Gets the length of the body of the current instance. + /// + /// + /// A containing the body length of the + /// current instance. + /// + public ushort BodyLength { + get {return body_length;} + } + #endregion + + + + #region Public Methods + + /// + /// Copies the body to another array. + /// + /// + /// A to copy the body to. + /// + /// + /// A specifying at what index to start + /// copying. + /// + /// + /// is . + /// + /// + /// is less than zero or does + /// not provide enough space to copy the body. + /// + public void CopyTo (byte[] dest, int destIndex) + { + if (dest == null) + throw new ArgumentNullException ("dest"); + + if (body_length > dest.Length - destIndex) + throw new ArgumentOutOfRangeException ( + "destIndex"); + + Array.Copy (data, body_index, dest, destIndex, + body_length); + } + + /// + /// Gets the body data of with the current instance. + /// + /// + /// A new containing the body data of + /// the current instance. + /// + public byte[] GetBody () + { + byte[] body_data = new byte [body_length]; + Array.Copy (data, body_index, body_data, 0, + body_length); + return body_data; + } + + /// + /// Creates and returns a + /// representation of the current instance. + /// + /// + /// A representation of the current + /// instance. + /// + public override string ToString () + { + return string.Format (CultureInfo.CurrentCulture, + Strings.Record_ToString, + Version, Type, RequestID, BodyLength); + } + + /// + /// Sends a FastCGI record with the data from the current + /// instance over a given socket. + /// + /// + /// A object to send the data over. + /// + public void Send (Socket socket) + { + Send (socket, null); + } + + /// + /// Sends a FastCGI record with the data from the current + /// instance over a given socket. + /// + /// + /// A object to send the data over. + /// + /// + /// A to write the record to or to create a temporary buffer during + /// the send. + /// + /// + /// If is of insufficient size to + /// write to the buffer, a temporary buffer will be created. + /// + public void Send (Socket socket, byte [] buffer) + { + byte padding_size = (byte) ((8 - (body_length % 8)) % 8); + + int total_size = 8 + body_length + padding_size; + + byte[] data = (buffer != null && buffer.Length > + total_size) ? buffer : new byte [total_size]; + + data [0] = version; + data [1] = (byte) type; + data [2] = (byte) (request_id >> 8); + data [3] = (byte) (request_id & 0xFF); + data [4] = (byte) (body_length >> 8); + data [5] = (byte) (body_length & 0xFF); + data [6] = padding_size; + + Array.Copy (this.data, body_index, data, 8, + body_length); + + for (int i = 0; i < padding_size; i ++) + data [8 + body_length + i] = 0; + + Logger.Write (LogLevel.Debug, + Strings.Record_Sent, + Type, RequestID, body_length); + + SendAll (socket, data, total_size); + } + + #endregion + + + + #region Internal Static Methods + + /// + /// Reads two bytes of data from an array and returns the + /// appropriate value. + /// + /// + /// A containing an array of data to + /// read from. + /// + /// + /// A specifying the index in the array at + /// which to start reading. + /// + internal static ushort ReadUInt16 (byte [] array, + int arrayIndex) + { + ushort value = array [arrayIndex]; + value = (ushort) (value << 8); + value += array [arrayIndex + 1]; + return value; + } + + #endregion + + + + #region Private Static Methods + + private static void ReceiveAll (Socket socket, byte [] data, int length) + { + if (length <= 0) + return; + + int total = 0; + while (total < length) { + total += socket.Receive (data, total, + length - total, + System.Net.Sockets.SocketFlags.None); + } + } + + private static void SendAll (Socket socket, byte [] data, int length) + { + if (length <= 0) + return; + + int total = 0; + while (total < length) { + total += socket.Send (data, total, + length - total, + System.Net.Sockets.SocketFlags.None); + } + } + + #endregion + } +} diff --git a/src/Mono.WebServer.FastCgi/Request.cs b/src/Mono.WebServer.FastCgi/Request.cs new file mode 100644 index 0000000..ba4512a --- /dev/null +++ b/src/Mono.WebServer.FastCgi/Request.cs @@ -0,0 +1,1061 @@ +// +// Requests/Request.cs: Handles FastCGI requests. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// Robert Jordan +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; +#if NET_2_0 +using System.Collections.Generic; +#else +using System.Collections; +#endif +using System.IO; +using System.Text; + +namespace Mono.FastCgi { + public class Request + { + #region Private Fields + + /// + /// Contains the request ID to use when sending data. + /// + private ushort requestID; + + /// + /// Contains the object object from + /// which data is received and to which data will be sent. + /// + private Connection connection; + + #endregion + + + + #region Constructors + + /// + /// Constructs and initializes a new instance of with the specified request ID and + /// connection. + /// + /// + /// A containing the request ID of the + /// new instance. + /// + /// + /// A object from which data is + /// received and to which data will be sent. + /// + public Request (ushort requestID, Connection connection) + { + this.requestID = requestID; + this.connection = connection; + } + #endregion + + + + #region Request Completion Handling + + // When a request is completed, the output and error streams + // must be completed and the final farewell must be send so + // the HTTP server can finish its response. + + /// + /// Completes the request by closing any opened response and + /// error streams and sending the EndRequest record to the + /// client. + /// + /// + /// A containing the application + /// status the request ended with. + /// This is the same value as would be returned by a + /// program on termination. On successful termination, this + /// would be zero. + /// + /// + /// A containing the FastCGI + /// protocol status with which the request is being ended. + /// + /// + /// To close the request, this method calls , which additionally + /// releases the resources so they can be garbage collected. + /// + public void CompleteRequest (int appStatus, + ProtocolStatus protocolStatus) + { + // Data is no longer needed. + DataNeeded = false; + + // Close the standard output if it was opened. + if (stdout_sent) + SendStreamData (RecordType.StandardOutput, + new byte [0], 0); + + // Close the standard error if it was opened. + if (stderr_sent) + SendStreamData (RecordType.StandardError, + new byte [0], 0); + + connection.EndRequest (requestID, appStatus, + protocolStatus); + } + + /// + /// Aborts the request by sending a message on the error + /// stream, logging it, and completing the request with an + /// application status of -1. + /// + /// + /// A containing the error message. + /// + /// + /// A containing argument to insert + /// into the message. + /// + public void Abort (string message, params object [] args) + { + SendError (message); + Logger.Write (LogLevel.Error, + Strings.Request_Aborting, RequestID); + Logger.Write (LogLevel.Error, message, args); + CompleteRequest (-1, ProtocolStatus.RequestComplete); + } + + /// + /// Indicates whether or not data is needed by the current + /// instance. + /// + private bool data_needed = true; + + /// + /// Gets and sets whether or not data is still needed by the + /// current instance. + /// + /// + /// A indicating whether or not data is + /// still needed by the current instance. + /// + /// + /// This value is used by the connection to determine whether + /// or not it still needs to receive data for the request + /// from the socket. As soon as a request has received all + /// the necessary data, it should set the value to so the connection can continue on + /// with its next task. + /// + public bool DataNeeded { + get {return data_needed;} + protected set {data_needed = value;} + } + + /// + /// Gets the server that spawned the connection used by the + /// current instance. + /// + /// + /// A containing the server + /// that spawned the connection used by the current instance. + /// + public Server Server { + get {return connection.Server;} + } + + /// + /// Gets the request ID of the current instance as used by + /// the connection. + /// + /// + /// A containing the request ID of the + /// current instance. + /// + public ushort RequestID { + get {return requestID;} + } + + /// + /// Gets whether or not the connection used by the current + /// instance is connected. + /// + /// + /// A indicating whether or not the + /// connection used by the current instance is connected. + /// + /// + /// If the connection is not connected, any response data is + /// disregarded. As such, before any intense operation, this + /// value should be checked as to avoid any unneccessary + /// work. + /// + public bool IsConnected { + get {return connection.IsConnected;} + } + #endregion + + + + #region Request Details + + /// + /// Contains the host name, after is + /// called. + /// + private string vhost = null; + + /// + /// Contains the port number, after + /// is called. + /// + private int port = -1; + + /// + /// Contains the path, after is called. + /// + private string path = null; + + /// + /// Contains the physical path, after is called. + /// + private string rpath = null; + + /// + /// Gets the host name used to make the request handled by + /// the current instance. + /// + /// + /// A containing the host name used to + /// make the request handled by the current instance. + /// + public string HostName { + get { + if (vhost == null) + vhost = GetParameter ("HTTP_HOST"); + + return vhost; + } + } + + /// + /// Gets the port number used to make the request handled by + /// the current instance. + /// + /// + /// A containing the port number used to + /// make the request handled by the current instance. + /// + public int PortNumber { + get { + if (port < 0) + port = int.Parse (GetParameter ( + "SERVER_PORT")); + + return port; + } + } + + /// + /// Gets the virtual path used to make the request handled by + /// the current instance. + /// + /// + /// A containing the virtual path used + /// to make the request handled by the current instance. + /// + public string Path { + get { + if (path == null) + path = GetParameter ("SCRIPT_NAME"); + + return path; + } + } + + /// + /// Gets the physical path mapped to by the current instance. + /// + /// + /// A containing the physical path + /// mapped to by current instance. + /// + public string PhysicalPath { + get { + if (rpath == null) + rpath = GetParameter ("SCRIPT_FILENAME"); + + return rpath; + } + } + + #endregion + + + + #region Parameter Handling + + /// + /// This event is called when the parameter data has been + /// completely read and parsed by the current instance. + /// + protected event EventHandler ParameterDataCompleted; + + /// + /// Contains the paramter data as it is received from the + /// server. Upon completion, the parameter data is parsed and + /// the value is set to . + /// + #if NET_2_0 + List parameter_data = new List (); + #else + ArrayList parameter_data = new ArrayList (); + #endif + + /// + /// Contains the name/value pairs as they have been parsed. + /// + #if NET_2_0 + IDictionary parameter_table = null; + #else + IDictionary parameter_table = null; + #endif + + /// + /// Adds a block of FastCGI parameter data to the current + /// instance. + /// + /// + /// A containing a chunk of parameter + /// data. + /// + /// + /// In the standard FastCGI method, if the data + /// received has a length of zero, the parameter data has + /// been completed and the the data will be parsed. At that + /// point will be + /// called. + /// If an exception is encountered while parsing the + /// parameters, the request will be aborted. + /// + /// + /// is . + /// + /// + /// The parameter data has already been completed and parsed. + /// + public void AddParameterData (byte [] data) + { + // Validate arguments in public methods. + if (data == null) + throw new ArgumentNullException ("data"); + + // When all the parameter data is received, it is acted + // on and the parameter_data object is nullified. + // Further data suggests a problem with the HTTP server. + if (parameter_data == null) { + Logger.Write (LogLevel.Warning, + Strings.Request_ParametersAlreadyCompleted); + return; + } + + // If data was provided, append it to that already + // received, and exit. + if (data.Length > 0) { + parameter_data.AddRange (data); + return; + } + + // A zero length record indicates the end of that form + // of data. When it is received, the data can then be + // examined and worked on. + + #if NET_2_0 + data = parameter_data.ToArray (); + #else + data = (byte []) parameter_data.ToArray (typeof (byte)); + #endif + + try { + parameter_table = NameValuePair.FromData (data); + // The parameter data is no longer needed and + // can be sent to the garbage collector. + parameter_data = null; + + // Inform listeners of the completion. + if (ParameterDataCompleted != null) + ParameterDataCompleted (this, + EventArgs.Empty); + } catch { + Abort (Strings.Request_CanNotParseParameters); + } + + ParseParameterData (); + } + + /// + /// Parses the parameters and tries to deduce SCRIPT_NAME & PATH_INFO + /// from several other params supplied by the web server. + /// Required by Apache. + /// + void ParseParameterData () + { + string scriptName = GetParameter ("SCRIPT_NAME"); + if (scriptName == null || scriptName == String.Empty) + return; + + string pathInfo = GetParameter ("PATH_INFO"); + if (pathInfo == null || pathInfo == String.Empty) + return; + + string pathTranslated = GetParameter ("PATH_TRANSLATED"); + if (pathTranslated == null || pathTranslated == String.Empty) + return; + + string documentRoot = GetParameter ("DOCUMENT_ROOT"); + if (documentRoot == null || documentRoot == String.Empty) + return; + + string redirectUrl = GetParameter ("REDIRECT_URL"); + if (redirectUrl == null || redirectUrl == String.Empty) + return; + + if (pathInfo != redirectUrl) + return; + + string[] parts = pathInfo.Split ('/'); + // we expect at least "/" + path-component. + if (parts.Length < 2) + return; + + // at this point we have: + // + // REDIRECT_URL=/dir/test.aspx/foo + // PATH_INFO=/dir/test.aspx/foo + // PATH_TRANSLATED=/srv/www/htdoc/dir/test.aspx/foo + // SCRIPT_NAME=/cgi-bin/fastcgi-mono-server + // SCRIPT_FILENAME=/srv/www/cgi-bin/fastcgi-mono-server + + for (int i = 1; i < parts.Length; i++) { + string vpath = Combine (parts, 1, i); + string ppath = System.IO.Path.GetFullPath (documentRoot + vpath); + bool isFile = File.Exists (ppath); + bool isDir = Directory.Exists (ppath); + + if (!(isFile || isDir)) + break; + + if (isFile || i == parts.Length - 1) { + // now we set: + // + // PATH_INFO=/foo + // PATH_TRANSLATED=/srv/www/htdoc/dir/foo + // SCRIPT_NAME=/dir/test.aspx + // SCRIPT_FILENAME=/srv/www/htdocs/dir/test.aspx + + SetParameter ("SCRIPT_NAME", vpath); + SetParameter ("SCRIPT_FILENAME", ppath); + + if (i == parts.Length - 1) { + SetParameter ("PATH_INFO", null); + SetParameter ("PATH_TRANSLATED", null); + } else { + string pt = Combine (parts, i + 1, parts.Length - 1); + SetParameter ("PATH_INFO", pt); + SetParameter ("PATH_TRANSLATED", + System.IO.Path.GetFullPath (documentRoot + pt)); + } + break; + } + } + } + + /// + /// Combines the specified virtual path components. + /// + /// The path components. + /// The index inside "paths" to start with. + /// The index inside "paths" to end with. + static string Combine (string[] paths, int startIndex, int endIndex) + { + StringBuilder b = new StringBuilder (); + for (int i = startIndex; i <= endIndex; i++) { + b.Append ('/'); + b.Append (paths [i]); + } + return b.ToString (); + } + + /// + /// Gets a parameter with a specified name. + /// + /// + /// A containing a parameter namte to + /// find in current instance. + /// + /// + /// A containing the parameter with the + /// specified name, or if it was not + /// found. + /// + /// + /// This method is analogous to as FastCGI parameters represent environment variables + /// that would be passed to a CGI/1.1 program. + /// + public string GetParameter (string parameter) + { + #if NET_2_0 + if (parameter_table != null && + parameter_table.ContainsKey (parameter)) + #else + if (parameter_table != null && + parameter_table.Contains (parameter)) + #endif + return (string) parameter_table [parameter]; + + return null; + } + + void SetParameter (string name, string value) + { + if (parameter_table != null) + parameter_table [name] = value; + } + + /// + /// Gets all parameter contained in the current instance. + /// + /// + #if NET_2_0 + /// A + #else + /// A + #endif + /// containing all the parameters contained in the current + /// instance. + /// + /// + /// This method is analogous to as + /// FastCGI parameters represent environment variables that + /// would be passed to a CGI/1.1 program. + /// + #if NET_2_0 + public IDictionary GetParameters () + #else + public IDictionary GetParameters () + #endif + { + return parameter_table; + } + + #endregion + + + + #region Standard Input Handling + + /// + /// This event is called when standard input data has been + /// received by the current instance. + /// + /// + /// Input data is analogous to standard input in CGI/1.1 + /// programs and contains post data from the HTTP request. + /// + protected event DataReceivedHandler InputDataReceived; + + /// + /// Indicates whether or not the standard input data has + /// been completely read by the current instance. + /// + private bool input_data_completed = false; + + /// + /// Adds a block of standard input data to the current + /// instance. + /// + /// + /// A containing a block of input + /// data. + /// + /// + /// Input data is analogous to standard input in + /// CGI/1.1 programs and contains post data from the HTTP + /// request. + /// When data is received, is called. + /// + /// + /// does not have the type . + /// + /// + /// The input data has already been completed. + /// + public void AddInputData (Record record) + { + // Validate arguments in public methods. + if (record.Type != RecordType.StandardInput) + throw new ArgumentException ( + Strings.Request_NotStandardInput, + "record"); + + // There should be no data following a zero byte record. + if (input_data_completed) { + Logger.Write (LogLevel.Warning, + Strings.Request_StandardInputAlreadyCompleted); + return; + } + + if (record.BodyLength == 0) + input_data_completed = true; + + // Inform listeners of the data. + if (InputDataReceived != null) + InputDataReceived (this, new DataReceivedArgs (record)); + } + #endregion + + + + #region File Data Handling + + /// + /// This event is called when file data has been received by + /// the current instance. + /// + /// + /// File data send for the FastCGI filter role and contains + /// the contents of the requested file to be filtered. + /// + protected event DataReceivedHandler FileDataReceived; + + /// + /// Indicates whether or not the file data has been + /// completely read by the current instance. + /// + private bool file_data_completed = false; + + /// + /// Adds a block of file data to the current instance. + /// + /// + /// A containing a block of file + /// data. + /// + /// + /// File data send for the FastCGI filter role and + /// contains the contents of the requested file to be + /// filtered. + /// When data is received, is called. + /// + /// + /// does not have the type . + /// + /// + /// The file data has already been completed. + /// + public void AddFileData (Record record) + { + // Validate arguments in public methods. + if (record.Type != RecordType.Data) + throw new ArgumentException ( + Strings.Request_NotFileData, + "record"); + + // There should be no data following a zero byte record. + if (file_data_completed) { + Logger.Write (LogLevel.Warning, + Strings.Request_FileDataAlreadyCompleted); + return; + } + + if (record.BodyLength == 0) + file_data_completed = true; + + // Inform listeners of the data. + if (FileDataReceived != null) + FileDataReceived (this, new DataReceivedArgs (record)); + } + #endregion + + + + #region Standard Output Handling + + /// + /// Indicates whether or not output data has been sent. + /// + bool stdout_sent = false; + + /// + /// Sends a specified number of bytes of standard output + /// data. + /// + /// + /// A containing output data to send. + /// + /// + /// A containing the number of bytes of + /// to send. + /// + /// + /// FastCGI output data is analogous to CGI/1.1 + /// standard output data. + /// + public void SendOutput (byte [] data, int length) + { + if (data == null) + throw new ArgumentNullException ("data"); + + if (data.Length == 0) + return; + + stdout_sent = true; + + SendStreamData (RecordType.StandardOutput, data, length); + } + + /// + /// Sends standard output data. + /// + /// + /// A containing output data to send. + /// + /// + /// FastCGI output data is analogous to CGI/1.1 + /// standard output data. + /// To send text, use . + /// To send only the beginning of a (as in the case of buffers), use . + /// + public void SendOutput (byte [] data) + { + SendOutput (data, data.Length); + } + + /// + /// Sends standard outpu text in UTF-8 encoding. + /// + /// + /// A containing text to send. + /// + /// + /// FastCGI output data is analogous to CGI/1.1 + /// standard output data. + /// To specify the text encoding, use . + /// + public void SendOutputText (string text) + { + SendOutput (text, System.Text.Encoding.UTF8); + } + + /// + /// Sends standard output text in a specified encoding. + /// + /// + /// A containing text to send. + /// + /// + /// A containing a + /// encoding to use when converting the text. + /// + /// + /// FastCGI output data is analogous to CGI/1.1 + /// standard output data. + /// + public void SendOutput (string text, System.Text.Encoding encoding) + { + SendOutput (encoding.GetBytes (text)); + } + #endregion + + + + #region Standard Error Handling + + /// + /// Indicates whether or not error data has been sent. + /// + bool stderr_sent = false; + + /// + /// Sends a specified number of bytes of standard error data. + /// + /// + /// A containing error data to send. + /// + /// + /// A containing the number of bytes of + /// to send. + /// + /// + /// FastCGI error data is analogous to CGI/1.1 standard + /// error data. + /// + public void SendError (byte [] data, int length) + { + if (data == null) + throw new ArgumentNullException ("data"); + + if (data.Length == 0) + return; + + stderr_sent = true; + + SendStreamData (RecordType.StandardError, data, length); + } + + /// + /// Sends standard error data. + /// + /// + /// A containing error data to send. + /// + /// + /// FastCGI error data is analogous to CGI/1.1 standard + /// error data. + /// To send text, use . + /// To send only the beginning of a (as in the case of buffers), use . + /// + public void SendError (byte [] data) + { + SendError (data, data.Length); + } + + /// + /// Sends standard error text in UTF-8 encoding. + /// + /// + /// A containing text to send. + /// + /// + /// FastCGI error data is analogous to CGI/1.1 standard + /// error data. + /// To specify the text encoding, use . + /// + public void SendError (string text) + { + SendError (text, System.Text.Encoding.UTF8); + } + + /// + /// Sends standard error text in a specified encoding. + /// + /// + /// A containing text to send. + /// + /// + /// A containing a + /// encoding to use when converting the text. + /// + /// + /// FastCGI error data is analogous to CGI/1.1 standard + /// error data. + /// + public void SendError (string text, + System.Text.Encoding encoding) + { + SendError (encoding.GetBytes (text)); + } + #endregion + + + + #region Private Methods + + /// + /// Sends a block of data with a specified length to the + /// client in a specified type of record, splitting it into + /// smaller records if the data is too large. + /// + /// + /// A containing the type of + /// record to send the data in. + /// + /// + /// A containing the data to send. + /// + /// + /// A containing the length of the data to + /// send. If greater than the length of , it will decreased to that size. + /// + private void SendStreamData (RecordType type, byte [] data, + int length) + { + // Records are only able to hold 65535 bytes of data. If + // larger data is to be sent, it must be broken into + // smaller components. + + if (length > data.Length) + length = data.Length; + + int max_size = 0x7fff; + + if (length < max_size) + connection.SendRecord (type, requestID, data, 0, + length); + else + { + int index = 0; + while (index < length) + { + int chunk_length = (max_size < + length - index) ? max_size : + (length - index); + + connection.SendRecord (type, requestID, + data, index, chunk_length); + + index += chunk_length; + } + } + } + #endregion + } + + /// + /// This delegate is used for notification that data has been + /// received, typically by . + /// + /// + /// A object that sent the event. + /// + /// + /// A object containing the arguments + /// for the event. + /// + public delegate void DataReceivedHandler (Request sender, + DataReceivedArgs args); + + /// + /// This class extends and provides + /// arguments for the event that data is received. + /// + public class DataReceivedArgs : EventArgs + { + /// + /// Contains the data that was received. + /// + private Record record; + + /// + /// Constructs and initializes a new instance of with the specified data. + /// + /// + /// A containing the data that was + /// received. + /// + public DataReceivedArgs (Record record) + { + this.record = record; + } + + /// + /// Gets whether or not the data has been completed. + /// + /// + /// A indicating whether or not the data + /// has been completed. + /// + /// + /// Data completeness means that this is that last event + /// of this type coming from the sender. It is the standard + /// FastCGI test equivalent to args.Data.Length == + /// 0. + /// + public bool DataCompleted { + get {return record.BodyLength == 0;} + } + + /// + /// Gets the data that was received. + /// + /// + /// A containing the data that was + /// received. + /// + public byte[] GetData () + { + return record.GetBody (); + } + + /// + /// Gets the length of the data in the current instance. + /// + /// + /// A containing the length of the data + /// in the current instance. + /// + public int DataLength { + get {return record.BodyLength;} + } + + /// + /// Copies the data to another array. + /// + /// + /// A to copy the body to. + /// + /// + /// A specifying at what index to start + /// copying. + /// + /// + /// is . + /// + /// + /// is less than zero or does + /// not provide enough space to copy the body. + /// + public void CopyTo (byte[] dest, int destIndex) + { + if (dest == null) + throw new ArgumentNullException ("dest"); + + if (DataLength > dest.Length - destIndex) + throw new ArgumentOutOfRangeException ( + "destIndex"); + + record.CopyTo (dest, destIndex); + } + } +} diff --git a/src/Mono.WebServer.FastCgi/Responder.cs b/src/Mono.WebServer.FastCgi/Responder.cs new file mode 100644 index 0000000..210855b --- /dev/null +++ b/src/Mono.WebServer.FastCgi/Responder.cs @@ -0,0 +1,169 @@ +// +// Responder.cs: Resonds to FastCGI "Responder" requests with an ASP.NET +// application. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; +#if NET_2_0 +using System.Collections.Generic; +#else +using System.Collections; +#endif +using Mono.FastCgi; +using System.Text; + +namespace Mono.WebServer.FastCgi +{ + public class Responder : MarshalByRefObject, IResponder + { + private static string error500 = + "HTTP/1.0 500 No Application Found\r\n" + + "Content-type: text/html\r\n" + + "Connection: close\r\n\r\n" + + "\r\n" + + " \r\n" + + " 500 No Application Found\r\n" + + " \r\n" + + " \r\n" + + "

No Application Found

\r\n" + + "

The server could not find our register\r\n" + + " an application matching the following\r\n" + + " characteristics:

\r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + "
Host{0}
Port{1}
Request Path{2}
Physical Path{3}
\r\n" + + " \r\n" + + "\r\n"; + + private ResponderRequest request; + + public Responder (ResponderRequest request) + { + this.request = request; + } + + public int Process () + { + // Uncommenting the following lines will cause the page + // + headers to be rendered as plain text. (Pretty sweet + // for debugging.) + /* + request.SendOutputText ("Content-type: text/plain\r\n\r\n"); + request.SendOutputText ("Output:\r\n"); + request.SendOutputText (Path + "\r\n"); + request.SendOutputText (PhysicalPath + "\r\n"); + */ + + VPathToHost host = Server.GetApplicationForPath ( + HostName, PortNumber, Path, PhysicalPath); + + // If the host is null, the server was unable to + // determine a sane plan. Alert the client. + if (host == null) { + request.SendOutputText (string.Format (error500, + HostName, PortNumber, + Path, PhysicalPath)); + return -1; + } + + try { + ((ApplicationHost)host.AppHost).ProcessRequest (this); + } catch (Exception e) { + Logger.Write (LogLevel.Error, + "ERROR PROCESSING REQUEST: " + e); + return -1; + } + + // MIN_VALUE means don't close. + return int.MinValue; + } + + public ResponderRequest Request { + get {return request;} + } + + public void SendOutput(string text, System.Text.Encoding encoding) + { + request.SendOutput (text, encoding); + } + + public void SendOutput (byte [] data, int length) + { + request.SendOutput (data, length); + } + + public string GetParameter (string name) + { + return request.GetParameter (name); + } + + #if NET_2_0 + public IDictionary GetParameters () + #else + public IDictionary GetParameters () + #endif + { + return request.GetParameters (); + } + + public int RequestID { + get {return request.RequestID;} + } + + public byte [] InputData { + get {return request.InputData;} + } + + public void CompleteRequest (int appStatus) + { + request.CompleteRequest (appStatus, ProtocolStatus.RequestComplete); + } + + public bool IsConnected { + get {return request.IsConnected;} + } + + public string HostName { + get {return request.HostName;} + } + + public int PortNumber { + get {return request.PortNumber;} + } + + public string Path { + get {return request.Path;} + } + + public string PhysicalPath { + get {return request.PhysicalPath;} + } + } +} diff --git a/src/Mono.WebServer.FastCgi/ResponderRequest.cs b/src/Mono.WebServer.FastCgi/ResponderRequest.cs new file mode 100644 index 0000000..30c5f3d --- /dev/null +++ b/src/Mono.WebServer.FastCgi/ResponderRequest.cs @@ -0,0 +1,292 @@ +// +// Requests/ResponderRequest.cs: Handles FastCGI requests for a responder. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; +using System.Globalization; +using System.Threading; + +namespace Mono.FastCgi { + /// + /// This class extends adding on processing + /// features for the role of Responder. + /// + public class ResponderRequest : Request + { + #region Private Fields + + /// + /// Contains the standard input data for the request. + /// + /// + /// When the input is first read, an array of a size equal to + /// CONTENT_LENGTH is created and assigned. At that point + /// is set to zero and the array + /// is filled piece by piece as more input data is received. + /// + private byte [] input_data; + + /// + /// Contains the index at which to write the next block of + /// data in . + /// + private int write_index; + + /// + /// Contains the that will respond + /// to the current instance. + /// + private IResponder responder; + + #endregion + + + + #region Constructors + + /// + /// Constructs and initializes a new instance of with the specified request ID + /// and connection. + /// + /// + /// A containing the request ID of the + /// new instance. + /// + /// + /// A object from which data is + /// received and to which data will be sent. + /// + public ResponderRequest (ushort requestID, + Connection connection) + : base (requestID, connection) + { + if (!Server.SupportsResponder) + throw new Exception (); + + responder = Server.CreateResponder (this); + + InputDataReceived += OnInputDataReceived; + } + + #endregion + + + + #region Public Properties + + /// + /// Gets the standard input data sent to the current + /// instance. + /// + /// + /// The size of this input will be equal to the value of + /// the CONTENT_LENGTH parameter but will contain zeroed + /// values at the end before the data is completely read and + /// is called. + /// + public byte [] InputData { + get {return input_data != null ? input_data : new byte [0];} + } + + #endregion + + + + #region Private Methods + + /// + /// Handles the + /// event by completing the request is the data is + /// completed, or creating if it + /// does not exist and filling with + /// the received data. + /// + /// + /// A containing the sender of the + /// event. Always the current instance. + /// + /// + /// A containing the + /// arguments for the event. + /// + private void OnInputDataReceived (Request sender, + DataReceivedArgs args) + { + // If the data is completed, call the worker and return. + if (args.DataCompleted) { + DataNeeded = false; + + if (input_data != null && + write_index < input_data.Length) { + Abort (Strings.ResponderRequest_IncompleteInput, + write_index, input_data.Length); + } + else if (Server.MultiplexConnections) + ThreadPool.QueueUserWorkItem (Worker); + else + Worker (null); + + return; + } + + // If input_data is null, create the new array by + // reading the length from the CONTENT_LENGTH parameter. + if (input_data == null) { + string length_text = this.GetParameter + ("CONTENT_LENGTH"); + + // If the field is missing we can't continue. + if (length_text == null) { + Abort (Strings.ResponderRequest_NoContentLength); + return; + } + + // If the length isn't a number, we can't + // continue. + int length; + try { + length = int.Parse (length_text, + CultureInfo.InvariantCulture); + } catch { + Abort (Strings.ResponderRequest_NoContentLengthNotNumber); + return; + } + + input_data = new byte [length]; + } + + if (write_index + args.DataLength > input_data.Length) + { + Abort (Strings.ResponderRequest_ContentExceedsLength); + return; + } + + args.CopyTo (input_data, write_index); + write_index += args.DataLength; + } + + /// + /// Processes the request with the responder. + /// + /// + /// A containing the state as passed to + /// the method. Always . + /// + /// + /// If multiplexing is enabled, this method will be called + /// from the thread queue. Otherwise it will be called in the + /// same thread as the connection. + /// + private void Worker (object state) + { + int appStatus = responder.Process (); + if (appStatus != int.MinValue) + CompleteRequest (appStatus, + ProtocolStatus.RequestComplete); + } + + #endregion + } + + /// + /// This interface is used for classes that will serve as responders. + /// + /// + /// In addition to implementing this interface, a potential + /// responder must contain a constructor accepting a single parameter + /// of type . + /// To register a responder with a server, use . + /// + /// + /// A very basic responder: + /// + /// class MyResponder : IResponder + /// { + /// ResponderRequest req; + /// + /// public MyResponder (ResponderRequest request) + /// { + /// req = request; + /// } + /// + /// public int Process () + /// { + /// req.SendOutput ("Content-Type: text/html\r\n\r\n"); + /// req.SendOutput ("<html>\n <head><title>Test</title></head>\n"); + /// req.SendOutput (" <body>\n Server name: "); + /// req.SendOutput (GetParameter ("SERVER_NAME")); + /// req.SendOutput ("\n </body>\n</html>"); + /// return 0; + /// } + /// + /// public ResponderRequest Request { + /// get {return req;} + /// } + /// } + /// + /// ... + /// + /// server.SetResponder (typeof (MyRequest)); + /// + /// + public interface IResponder + { + /// + /// Gets the request that the current instance is to respond + /// to. + /// + /// + /// A object containing the + /// request that the current instance is to respond to. + /// + ResponderRequest Request {get;} + + /// + /// Processes the request and performs the response. + /// + /// + /// A containing the application + /// status the request ended with. + /// This is the same value as would be returned by a + /// program on termination. On successful termination, this + /// would be zero. + /// + /// + /// In the event that the method spawns its own + /// thread for responding to the request, a value of will prevent the calling method + /// from completing the request. In that case, the thread + /// will be responsible for calling with the appropriate + /// application status and . + /// + int Process (); + } +} \ No newline at end of file diff --git a/src/Mono.WebServer.FastCgi/Server.cs b/src/Mono.WebServer.FastCgi/Server.cs new file mode 100644 index 0000000..f04c489 --- /dev/null +++ b/src/Mono.WebServer.FastCgi/Server.cs @@ -0,0 +1,761 @@ +// +// Server.cs: Accepts connections. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; +using System.Threading; +#if NET_2_0 +using System.Collections.Generic; +#else +using System.Collections; +#endif +using System.Globalization; + +namespace Mono.FastCgi { + /// + /// This class runs a FastCGI server and registers responder types. + /// + public class Server + { + #region Private Fields + + /// + /// Contains a list of the current connections. + /// + #if NET_2_0 + private List connections = new List (); + #else + private ArrayList connections = new ArrayList (); + #endif + + /// + /// Contains the socket to listen on. + /// + private Socket listen_socket; + + /// + /// Indicates whether or not the server is running. + /// + private bool started = false; + + /// + /// Indicates whether or not the server is currently + /// accepting a connection. + /// + private bool accepting = false; + + /// + /// Contains the thread used to create the server and + /// optionally keep it alive. + /// + private Thread runner; + + /// + /// Contains the lock used to regulate the accepting of + /// sockets. + /// + private object accept_lock = new object (); + + /// + /// Contains the async callback to be called when a socket is + /// accepted. + /// + private AsyncCallback accept_cb; + + /// + /// Contains the maximum number of connections to permit, as + /// mandated by the FastCGI specification. + /// + private int max_connections = int.MaxValue; + + /// + /// Contains the maximum number of requests to permit, as + /// mandated by the FastCGI specification. + /// + private int max_requests = int.MaxValue; + + /// + /// Indicates whether or not to multiplex connections to + /// permit, as mandated by the FastCGI specification. + /// + private bool multiplex_connections = false; + + /// + /// Contains the type of class to use for handling the + /// responder role. + /// + private System.Type responder_type = null; + + /// + /// Contains buffers to use for new connections. + /// + private byte [][] buffers = new byte [200][]; + + /// + /// Contains the number of buffers stored in . + /// + private int buffer_count; + + /// + /// Contains the lock to use when allocating and releasing + /// buffers. + /// + private object buffer_lock = new object (); + + #endregion + + + + #region Constructors + + /// + /// Constructs and initializes a new instance of + /// with a given socket. + /// + /// + /// A object to listen on. + /// + /// + /// is . + /// + public Server (Socket socket) + { + if (socket == null) + throw new ArgumentNullException ("socket"); + + this.listen_socket = socket; + } + + #endregion + + + + #region Public Properties + + /// + /// Gets and sets the maximum number of concurrent + /// connections the current instance will allow before it + /// stops accepting new ones. + /// + /// + /// A containing the number of + /// concurrent connections allowed by the current instance. + /// + /// + /// When the maximum number of connections has been reached, + /// the server will stop accepting connections until one of + /// the existing connection is terminated. + /// + /// + /// The value is less than 1. + /// + public int MaxConnections { + get {return max_connections;} + set { + if (value < 1) + throw new ArgumentOutOfRangeException ( + "value", + Strings.Server_MaxConnsOutOfRange); + + max_connections = value; + } + } + + /// + /// Gets and sets the maximum number of concurrent requests + /// the current instance will allow before it starts + /// rejecting new ones. + /// + /// + /// A containing the number of + /// concurrent requests allowed by the current instance. + /// + /// + /// When the maximum number of requests has been + /// reached, the server will respond to requests with the + /// FastCGI "Overloaded" end-of-request record. + /// In the case the connection multiplexing is + /// disabled, this property is redundant to + /// , as only one request is + /// permitted per connection. In such a case, this property + /// should be no less than to + /// avoid unnecessary connections. + /// + /// + /// The value is less than 1. + /// + public int MaxRequests { + get {return max_requests;} + set { + if (value < 1) + throw new ArgumentOutOfRangeException ( + "value", + Strings.Server_MaxReqsOutOfRange); + + max_requests = value; + } + } + + /// + /// Gets and sets whether or not the multiplexing of + /// requests is permitted in the current instance. + /// + /// + /// A indicating whether or not + /// multiplexing is permitted in the current instance. + /// + /// + /// Multiplexing of connections allows multiple requests and + /// responses to be sent simultaneously. This allows for + /// improved response times for multiple requests send over a + /// single connection. + /// + public bool MultiplexConnections { + get {return multiplex_connections;} + set {multiplex_connections = value;} + } + + /// + /// Gets whether or not the current instance can accept + /// another connection. + /// + /// + /// A indicating whether or not + /// the current instance will permit another connection. + /// + public bool CanAccept { + get {return started && ConnectionCount < max_connections;} + } + + /// + /// Gets whether or not the current instance can accept + /// another request. + /// + /// + /// A indicating whether or not + /// the current instance will permit another request. + /// + public bool CanRequest { + get {return started && RequestCount < max_requests;} + } + + /// + /// Gets the total number of open connections managed by the + /// current instance. + /// + /// + /// A containing the total number of + /// open connections managed by the current instance. + /// + public int ConnectionCount { + get {return connections.Count;} + } + + /// + /// Gets the total number of open requests managed by the + /// current instance. + /// + /// + /// A containing the total number of + /// open requests managed by the current instance. + /// + public int RequestCount { + get { + int requests = 0; + foreach (Connection c in connections) + requests += c.RequestCount; + + return requests; + } + } + + #endregion + + + + #region Public Methods + + /// + /// Starts the server in a different thread. + /// + /// + /// A specifying whether or not the + /// server thread should be run as a background thread. + /// + /// + /// The behavior of background and foreground threads + /// are identical except in that fact that the application + /// will not terminate while foreground threads are + /// running. + /// See for more + /// details. + /// + /// + /// The server is already started. + /// + public void Start (bool background) + { + if (started) + throw new InvalidOperationException ( + Strings.Server_AlreadyStarted); + + listen_socket.Listen (500); + + runner = new Thread (new ThreadStart (RunServer)); + runner.IsBackground = background; + runner.Start (); + } + + /// + /// Stops the server. + /// + /// + /// This closes all connections and aborts the thread. If + /// the thread is a foreground thread, this will allow the + /// program to terminate. + /// + /// + /// The server is not started. + /// + public void Stop () + { + if (!started) + throw new InvalidOperationException ( + Strings.Server_NotStarted); + + started = false; + listen_socket.Close (); + #if NET_2_0 + foreach (Connection c in new List ( + connections)) { + #else + foreach (Connection c in new ArrayList (connections)) { + #endif + EndConnection (c); + } + + runner.Abort (); + runner = null; + } + + /// + /// Ends a specified connection. + /// + /// + /// A object to terminate. + /// + /// + /// This method stops a connection by closing its + /// requests and listening sockets, permitting its thread to + /// terminate. + /// Once the connection is stopped, it is removed from + /// the list of managed connections, and if the server is not + /// accepting, begins the connection process. + /// + /// + /// is + /// . + /// + public void EndConnection (Connection connection) + { + if (connection == null) + throw new ArgumentNullException ("connection"); + + connection.Stop (); + + if (connections.Contains (connection)) + connections.Remove (connection); + + if (!accepting && CanAccept) + BeginAccept (); + } + + + /// + /// Gets name/value pairs for server variables. + /// + /// + #if NET_2_0 + /// A + #else + /// A + #endif + /// object containing FastCGI server variable names. + /// + /// + #if NET_2_0 + /// A + #else + /// A + #endif + /// object containing the server variables used by the + /// current instance. + /// + /// + /// A FastCGI client can at any time request + /// information on a collection of server variables. It + /// provides a list of variable names to which the server + /// responds with name/value pairs containing their + /// content. + /// + /// + /// is + /// . + /// + /// + /// contains a non-string value. + /// + #if NET_2_0 + public IDictionary GetValues (IEnumerable + names) + #else + public IDictionary GetValues (IEnumerable names) + #endif + { + if (names == null) + throw new ArgumentNullException ("names"); + + #if NET_2_0 + Dictionary pairs = + new Dictionary (); + foreach (string key in names) { + #else + Hashtable pairs = new Hashtable (); + foreach (object key in names) { + #endif + + string name = key as string; + + // We can't handle null values and we don't need + // to store the same value twice. + if (name == null || pairs.ContainsKey (name)) + continue; + + string value = null; + switch (name) + { + case "FCGI_MAX_CONNS": + value = max_connections.ToString ( + CultureInfo.InvariantCulture); + break; + + case "FCGI_MAX_REQS": + value = max_requests.ToString ( + CultureInfo.InvariantCulture); + break; + + case "FCGI_MPXS_CONNS": + value = multiplex_connections ? "1" : "0"; + break; + } + + if (value == null) { + Logger.Write (LogLevel.Warning, + Strings.Server_ValueUnknown, + key); + continue; + } + + pairs.Add (name, value); + } + + return pairs; + } + + /// + /// Allocates two buffers from the current instance for use + /// in sending and receiving records. + /// + /// + /// A of size . + /// + /// + /// A of size . + /// + /// + /// The current instance manages buffers to improve + /// performance. To release buffers back to the current + /// instance, use . + /// + public void AllocateBuffers (out byte [] buffer1, + out byte [] buffer2) + { + + buffer1 = null; + buffer2 = null; + + lock (buffer_lock) { + // If there aren't enough existing buffers, + // create new ones. + if (buffer_count < 2) + buffer1 = new byte [Record.SuggestedBufferSize]; + + if (buffer_count < 1) + buffer2 = new byte [Record.SuggestedBufferSize]; + + // Now that buffer1 and buffer2 may have been + // assigned to compensate for a lack of buffers + // in the array, loop through and assign the + // remaining values. + int length = buffers.Length; + for (int i = 0; i < length && (buffer1 == null || + buffer2 == null); i ++) { + if (buffers [i] != null) { + if (buffer1 == null) + buffer1 = buffers [i]; + else + buffer2 = buffers [i]; + + buffers [i] = null; + buffer_count --; + } + } + } + } + + /// + /// Releases two buffers back to the current instance. + /// + /// + /// A allocated by . + /// + /// + /// A allocated by . + /// + /// + /// The current instance manages buffers to improve + /// performance. To allocate buffers, use . + /// + public void ReleaseBuffers (byte [] buffer1, byte [] buffer2) + { + lock (buffer_lock) { + int length = buffers.Length; + foreach (byte [] buffer in new byte [][] {buffer1, buffer2}) { + if (buffer.Length < Record.SuggestedBufferSize) + continue; + + // If the buffer count is equal to the + // length of the buffer array, it needs + // to be enlarged. + if (buffer_count == length) { + byte [][] buffers_new = new byte [length + length / 3][]; + buffers.CopyTo (buffers_new, 0); + buffers = buffers_new; + buffers [buffer_count++] = buffer; + + if (buffer == buffer1) + buffers [buffer_count++] = buffer2; + + return; + } + + for (int i = 0; i < length && buffer != null; i++) { + if (buffers [i] == null) { + buffers [i] = buffer; + buffer_count ++; + break; + } + } + } + + } + } + + #endregion + + + + #region Private Methods + + /// + /// Starts the server by beginning an accept call. + /// + /// + /// If the server is running in the foreground, the thread + /// loops, waiting to be aborted. + /// + private void RunServer () + { + started = true; + accept_cb = new AsyncCallback (OnAccept); + listen_socket.BeginAccept (accept_cb, null); + if (runner.IsBackground) + return; + + while (true) // Just sleep until we're aborted. + Thread.Sleep (1000000); + } + + /// + /// Accepts a connection. + /// + /// + /// A containing the results of + /// the accept call. + /// + /// + /// Upon accepting the connection, it attempts to + /// create a object, attempts to + /// start the accept process again, and then runs the + /// connection. + /// The thread that evoked this method is used for the + /// duration of the connection. + /// + private void OnAccept (IAsyncResult ares) + { + Logger.Write (LogLevel.Debug, Strings.Server_Accepting); + Connection connection = null; + + lock (accept_lock) { + accepting = false; + } + + try { + Socket accepted = listen_socket.EndAccept (ares); + connection = new Connection (accepted, this); + connections.Add (connection); + } catch (System.Net.Sockets.SocketException e) { + Logger.Write (LogLevel.Error, + Strings.Server_AcceptFailed, e.Message); + if (e.ErrorCode == 10022) + Stop (); + } + + if (CanAccept) + BeginAccept (); + + if (connection != null) + connection.Run (); + } + + /// + /// Begins accepting a connection, unless one is already + /// being accepted. + /// + private void BeginAccept () + { + lock (accept_lock) { + if (accepting) + return; + + accepting = true; + listen_socket.BeginAccept (accept_cb, null); + } + } + + #endregion + + + + #region Responder Management + + /// + /// Sets the type to use for the + /// FastCGI responder role. + /// + /// + /// A for a class implementing the + /// interface. + /// + /// + /// does not implement the + /// interface or does not provide + /// the proper constructor. + /// + public void SetResponder (System.Type responder) + { + if (responder == null) { + responder_type = responder; + return; + } + + if (!typeof (IResponder).IsAssignableFrom (responder)) + throw new ArgumentException ( + Strings.Server_ResponderDoesNotImplement, + "responder"); + + // Checks that the correct constructor is available. + if (responder.GetConstructor (new System.Type[] + {typeof (ResponderRequest)}) == null) { + + string msg = string.Format ( + CultureInfo.CurrentCulture, + Strings.Server_ResponderLacksProperConstructor, + responder); + + throw new ArgumentException (msg, "responder"); + } + + responder_type = responder; + } + + /// + /// Creates a new object for a + /// specified request. + /// + /// + /// A object to create a + /// responder for. + /// + /// + /// A object for the provided + /// request. + /// + /// + /// The responder role is not supported. + /// + public IResponder CreateResponder (ResponderRequest request) + { + if (!SupportsResponder) + throw new InvalidOperationException ( + Strings.Server_ResponderNotSupported); + + return (IResponder) Activator.CreateInstance + (responder_type, new object [] {request}); + } + + /// + /// Gets whether or not the current instance supports + /// the role of FastCGI responder. + /// + /// + /// A indicating whether or not the + /// responder role is supported by the current instance. + /// + public bool SupportsResponder { + get {return responder_type != null;} + } + + #endregion + } +} diff --git a/src/Mono.WebServer.FastCgi/SocketFactory.cs b/src/Mono.WebServer.FastCgi/SocketFactory.cs new file mode 100644 index 0000000..e044ed4 --- /dev/null +++ b/src/Mono.WebServer.FastCgi/SocketFactory.cs @@ -0,0 +1,119 @@ +// +// SocketFactory.cs: Creates bound sockets of various types to use. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; + +namespace Mono.FastCgi { + /// + /// This static class creates bound instances of to use for various implementations. + /// + public static class SocketFactory + { + /// + /// Creates a bound socket for a specified local IP end + /// point. + /// + /// + /// A containing the + /// local IP end point to bind to. + /// + /// + /// A object bound to the specified + /// end point. + /// + /// + /// An error occurred while binding the socket. + /// + public static Socket CreateTcpSocket (System.Net.IPEndPoint + localEndPoint) + { + return new TcpSocket (localEndPoint); + } + + /// + /// Creates a bound socket for a specified IP address and + /// port. + /// + /// + /// A containing the + /// IP address be assigned. + /// + /// + /// A containing the port to bind to. + /// + /// + /// A object bound to the specified + /// IP address and end point. + /// + /// + /// An error occurred while binding the socket. + /// + public static Socket CreateTcpSocket (System.Net.IPAddress + address, int port) + { + return new TcpSocket (address, port); + } + + /// + /// Creates a unix socket for a specified path. + /// + /// + /// A containing the path to use. + /// + /// + /// A object bound to the specified + /// path. + /// + /// + /// An error occurred while binding the socket. + /// + public static Socket CreateUnixSocket (string path) + { + return new UnixSocket (path); + } + + /// + /// Creates a socket from a bound unmanaged socket. + /// + /// + /// A pointing to the bound socket. + /// + /// + /// A object bound to the specified + /// IP address and end point. + /// + /// + /// The specified socket is not bound. + /// + public static Socket CreatePipeSocket (IntPtr sock) + { + return new UnmanagedSocket (sock); + } + } +} diff --git a/src/Mono.WebServer.FastCgi/StandardSocket.cs b/src/Mono.WebServer.FastCgi/StandardSocket.cs new file mode 100644 index 0000000..61319ef --- /dev/null +++ b/src/Mono.WebServer.FastCgi/StandardSocket.cs @@ -0,0 +1,95 @@ +// +// SocketAbstractions/StandardSocket.cs: Provides a wrapper around a standard +// .NET socket. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; + +namespace Mono.FastCgi { + internal class StandardSocket : Socket + { + private System.Net.Sockets.Socket socket; + + public StandardSocket (System.Net.Sockets.Socket socket) + { + if (socket == null) + throw new ArgumentNullException ("socket"); + + this.socket = socket; + } + + public StandardSocket (System.Net.Sockets.AddressFamily addressFamily, + System.Net.Sockets.SocketType socketType, + System.Net.Sockets.ProtocolType protocolType, + System.Net.EndPoint localEndPoint) + { + if (localEndPoint == null) + throw new ArgumentNullException ("localEndPoint"); + + socket = new System.Net.Sockets.Socket (addressFamily, socketType, + protocolType); + + socket.Bind (localEndPoint); + } + + public override void Close () + { + socket.Close (); + } + + public override int Receive (byte [] buffer, int offset, int size, System.Net.Sockets.SocketFlags flags) + { + return socket.Receive (buffer, offset, size, flags); + } + + public override int Send (byte [] data, int offset, int size, System.Net.Sockets.SocketFlags flags) + { + return socket.Send (data, offset, size, flags); + } + + public override void Listen (int backlog) + { + socket.Listen (backlog); + } + + public override IAsyncResult BeginAccept (AsyncCallback callback, + object state) + { + return socket.BeginAccept (callback, state); + } + + public override Socket EndAccept (IAsyncResult asyncResult) + { + return new StandardSocket (socket.EndAccept (asyncResult)); + } + + public override bool Connected { + get {return socket.Connected;} + } + + } +} \ No newline at end of file diff --git a/src/Mono.WebServer.FastCgi/Strings.cs b/src/Mono.WebServer.FastCgi/Strings.cs new file mode 100644 index 0000000..c4667cc --- /dev/null +++ b/src/Mono.WebServer.FastCgi/Strings.cs @@ -0,0 +1,47 @@ +namespace Mono.FastCgi { + internal static class Strings { + public static string Server_MaxConnsOutOfRange = "At least one connection must be permitted."; + public static string Server_MaxReqsOutOfRange = "At least one request must be permitted."; + public static string Server_AlreadyStarted = "The server is already started."; + public static string Server_NotStarted = "The server has not been started."; + public static string Server_ValueUnknown = "Unknown value, {0}, requested by client."; + public static string Server_Accepting = "Accepting an incoming connection."; + public static string Server_AcceptFailed = "Failed to accept connection. Reason: {0}"; + public static string Server_ResponderDoesNotImplement = "Responder must implement the FastCgi.IResponder interface."; + public static string Server_ResponderLacksProperConstructor = "Responder must contain public constructor {0}(ResponderRequest)"; + public static string Server_ResponderNotSupported = "Responder role is not supported."; + public static string Logger_Format = "[{0:u}] {1,-7} {2}"; + public static string Connection_BeginningRun = "Beginning to receive records on connection."; + public static string Connection_EndingRun = "Finished receiving records on connection."; + public static string Connection_RecordNotReceived = "Failed to receive record."; + public static string Connection_RequestAlreadyExists = "Request with given ID already exists."; + public static string Connection_RoleNotSupported = "{0} role not supported by server."; + public static string Connection_RequestDoesNotExist = "Request {0} does not exist."; + public static string Connection_AbortRecordReceived = "FastCGI Abort Request"; + public static string Connection_UnknownRecordType = "Unknown type, {0}, encountered."; + public static string Connection_Terminating = "Terminating connection."; + public static string NameValuePair_ParameterRead = "Read parameter. ({0} = {1})"; + public static string NameValuePair_DuplicateParameter = "Duplicate name, {0}, encountered. Overwriting existing value."; + public static string NameValuePair_DictionaryContainsNonString = "Dictionary must only contain string values."; + public static string NameValuePair_LengthLessThanZero = "Length must be greater than or equal to zero."; + public static string Record_Received = "Record received. (Type: {0}, ID: {1}, Length: {2})"; + public static string Record_Sent = "Record sent. (Type: {0}, ID: {1}, Length: {2})"; + public static string Record_DataTooBig = "Data exceeds 65535 bytes and cannot be stored."; + public static string Record_ToString = "FastCGI Record:\n Version: {0}\n Type: {0}\n Request ID: {0}\n Content Length: {0}"; + public static string BeginRequestBody_WrongType = "The record's type is not BeginRequest."; + public static string BeginRequestBody_WrongSize = "8 bytes expected."; + public static string UnmanagedSocket_NotSupported = "Unmanaged sockets not supported."; + public static string UnixSocket_AlreadyExists = "There's already a server listening on {0}"; + public static string ResponderRequest_IncompleteInput = "Insufficient input data received. (Expected {0} bytes but got {1}.)"; + public static string ResponderRequest_NoContentLength = "Content length parameter missing."; + public static string ResponderRequest_NoContentLengthNotNumber = "Content length parameter not an integer."; + public static string ResponderRequest_ContentExceedsLength = "Input data exceeds content length."; + public static string Request_Aborting = "Aborting request {0}. Reason follows:"; + public static string Request_ParametersAlreadyCompleted = "The parameter stream has already been marked as closed. Ignoring record."; + public static string Request_StandardInputAlreadyCompleted = "The standard input stream has already been marked as closed. Ignoring record."; + public static string Request_FileDataAlreadyCompleted = "The file data stream has already been marked as closed. Ignoring record."; + public static string Request_CanNotParseParameters = "Failed to parse parameter data."; + public static string Request_NotStandardInput = "The record's type is not StandardInput."; + public static string Request_NotFileData = "The record's type is not Data."; + } +} \ No newline at end of file diff --git a/src/Mono.WebServer.FastCgi/TcpSocket.cs b/src/Mono.WebServer.FastCgi/TcpSocket.cs new file mode 100644 index 0000000..aca28f4 --- /dev/null +++ b/src/Mono.WebServer.FastCgi/TcpSocket.cs @@ -0,0 +1,46 @@ +// +// SocketAbstractions/TcpSocket.cs: Provides support for bound TCP sockets. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; + +namespace Mono.FastCgi { + internal class TcpSocket : StandardSocket + { + public TcpSocket (System.Net.IPEndPoint localEndPoint) + : base (System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, + System.Net.Sockets.ProtocolType.IP, localEndPoint) + { + } + + public TcpSocket (System.Net.IPAddress address, int port) + : this (new System.Net.IPEndPoint (address == System.Net.IPAddress.Any ? + System.Net.IPAddress.Loopback : address, port)) + { + } + } +} \ No newline at end of file diff --git a/src/Mono.WebServer.FastCgi/UnixSocket.cs b/src/Mono.WebServer.FastCgi/UnixSocket.cs new file mode 100644 index 0000000..209a5c7 --- /dev/null +++ b/src/Mono.WebServer.FastCgi/UnixSocket.cs @@ -0,0 +1,69 @@ +using System; +using System.Globalization; + +namespace Mono.FastCgi +{ + internal class UnixSocket : StandardSocket, IDisposable + { + string path = null; + + protected UnixSocket (Mono.Unix.UnixEndPoint localEndPoint) + : base (System.Net.Sockets.AddressFamily.Unix, + System.Net.Sockets.SocketType.Stream, + System.Net.Sockets.ProtocolType.IP, + localEndPoint) + { + } + + public UnixSocket (string path) : this (CreateEndPoint (path)) + { + this.path = path; + } + + + protected static Mono.Unix.UnixEndPoint CreateEndPoint (string path) + { + if (path == null) + throw new ArgumentNullException ("path"); + + Mono.Unix.UnixEndPoint ep = new Mono.Unix.UnixEndPoint ( + path); + + if (System.IO.File.Exists (path)) { + System.Net.Sockets.Socket conn = + new System.Net.Sockets.Socket ( + System.Net.Sockets.AddressFamily.Unix, + System.Net.Sockets.SocketType.Stream, + System.Net.Sockets.ProtocolType.IP); + + try { + conn.Connect (ep); + conn.Close (); + throw new InvalidOperationException ( + string.Format (CultureInfo.CurrentCulture, + Strings.UnixSocket_AlreadyExists, + path)); + } catch (System.Net.Sockets.SocketException) { + } + + System.IO.File.Delete (path); + } + + return ep; + } + + public void Dispose () + { + if (path != null) { + string f = path; + path = null; + System.IO.File.Delete (f); + } + } + + ~UnixSocket () + { + Dispose (); + } + } +} \ No newline at end of file diff --git a/src/Mono.WebServer.FastCgi/UnknownTypeBody.cs b/src/Mono.WebServer.FastCgi/UnknownTypeBody.cs new file mode 100644 index 0000000..6d1c75a --- /dev/null +++ b/src/Mono.WebServer.FastCgi/UnknownTypeBody.cs @@ -0,0 +1,88 @@ +// +// Record.cs: Represents the FastCGI BeginRequestBody structure. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; + +namespace Mono.FastCgi { + /// + /// This struct contains the body data for an UnknownType record. + /// + /// + /// An UnknownType record is sent by the server when the client sends + /// it a type that it does not know how to handle. + /// + public struct UnknownTypeBody + { + #region Private Fields + + /// + /// Contains the unknown type. + /// + private RecordType type; + + #endregion + + + + #region Constructors + + /// + /// Constructs and initializes a new instance of for a specified type. + /// + /// + /// A containing the unknown type. + /// + public UnknownTypeBody (RecordType unknownType) + { + type = unknownType; + } + + #endregion + + + + #region Public Methods + + /// + /// Gets the data contained in the current instance. + /// + /// + /// A containing the data contained in + /// the current instance. + /// + public byte [] GetData () + { + byte [] data = new byte [8]; + data [0] = (byte) type; + return data; + } + + #endregion + } +} diff --git a/src/Mono.WebServer.FastCgi/UnmanagedSocket.cs b/src/Mono.WebServer.FastCgi/UnmanagedSocket.cs new file mode 100644 index 0000000..f571ffc --- /dev/null +++ b/src/Mono.WebServer.FastCgi/UnmanagedSocket.cs @@ -0,0 +1,274 @@ +// +// SocketAbstractions/UnmanagedSocket.cs: Provides a wrapper around an unmanaged +// socket. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; +using sock=System.Net.Sockets; +using Mono.Unix.Native; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Mono.FastCgi { + internal class UnmanagedSocket : Socket + { + private IntPtr socket; + private bool connected = false; + + unsafe public UnmanagedSocket (IntPtr socket) + { + if (!supports_libc) + throw new NotSupportedException ( + Strings.UnmanagedSocket_NotSupported); + + if ((int) socket < 0) + throw new ArgumentException ("Invalid socket.", + "socket"); + + byte [] address = new byte [1024]; + int size = 1024; + fixed (byte* ptr = address) + if (getsockname (socket, ptr, ref size) != 0) + throw GetException (); + + + this.socket = socket; + } + + public override void Close () + { + connected = false; + if (shutdown (socket, (int) sock.SocketShutdown.Both) != 0) + throw GetException (); + } + + unsafe public override int Receive (byte [] buffer, int offset, + int size, + sock.SocketFlags flags) + { + if (!connected) + return 0; + + int value; + fixed (byte* ptr = buffer) + value = recv (socket, ptr + offset, size, (int) flags); + + if (value >= 0) + return value; + + connected = false; + throw GetException (); + } + + unsafe public override int Send (byte [] data, int offset, + int size, + sock.SocketFlags flags) + { + if (!connected) + return 0; + + int value; + fixed (byte* ptr = data) + value = send (socket, ptr + offset, size, + (int) flags); + + if (value >= 0) + return value; + + connected = false; + throw GetException (); + } + + public override void Listen (int backlog) + { + listen (socket, backlog); + } + + public override IAsyncResult BeginAccept (AsyncCallback callback, + object state) + { + SockAccept s = new SockAccept (socket, callback, state); + ThreadPool.QueueUserWorkItem (s.Run); + return s; + } + + public override Socket EndAccept (IAsyncResult asyncResult) + { + if (asyncResult == null) + throw new ArgumentNullException ("asyncResult"); + + SockAccept s = asyncResult as SockAccept; + if (s == null || s.socket != socket) + throw new ArgumentException ( + "Result was not produced by current instance.", + "asyncResult"); + + if (!s.IsCompleted) + s.AsyncWaitHandle.WaitOne (); + + if (s.except != null) + throw s.except; + + UnmanagedSocket u = new UnmanagedSocket (s.accepted); + u.connected = true; + return u; + } + + public override bool Connected { + get {return connected;} + } + + [DllImport ("libc", SetLastError=true, EntryPoint="shutdown")] + unsafe extern static int shutdown (IntPtr s, int how); + + [DllImport ("libc", SetLastError=true, EntryPoint="send")] + unsafe extern static int send (IntPtr s, byte *buffer, int len, + int flags); + + [DllImport ("libc", SetLastError=true, EntryPoint="recv")] + unsafe extern static int recv (IntPtr s, byte *buffer, int len, + int flags); + + [DllImport ("libc", SetLastError=true, EntryPoint="accept")] + unsafe extern static IntPtr accept (IntPtr s, byte *addr, + ref int addrlen); + + [DllImport("libc", SetLastError=true, EntryPoint="getsockname")] + unsafe static extern int getsockname(IntPtr s, byte *addr, + ref int namelen); + + [DllImport ("libc", SetLastError=true, EntryPoint="listen")] + unsafe extern static int listen (IntPtr s, int count); + + private class SockAccept : IAsyncResult + { + private bool completed = false; + private ManualResetEvent waithandle; + public IntPtr socket; + public IntPtr accepted; + public sock.SocketException except = null; + private AsyncCallback callback; + private object state; + + public SockAccept (IntPtr socket, AsyncCallback callback, + object state) + { + this.socket = socket; + this.callback = callback; + this.state = state; + } + + unsafe public void Run (object state) + { + byte[] address = new byte [1024]; + int size = 1024; + Errno errno; + fixed (byte* ptr = address) + do { + accepted = accept (socket, ptr, + ref size); + errno = Stdlib.GetLastError (); + } while ((int) accepted == -1 && + errno == Errno.EINTR); + + if ((int) accepted == -1) + except = GetException (errno); + + completed = true; + + if (waithandle != null) + waithandle.Set (); + + if (callback != null) + callback (this); + } + + public bool IsCompleted { + get {return completed;} + } + + public bool CompletedSynchronously { + get {return false;} + } + + public WaitHandle AsyncWaitHandle { + get { + lock (this) + if (waithandle == null) + waithandle = new ManualResetEvent (completed); + + return waithandle; + } + } + + public object AsyncState { + get {return state;} + } + } + + private static bool supports_libc; + + static UnmanagedSocket () + { + try { + string os = ""; + using (Stream st = File.OpenRead ( + "/proc/sys/kernel/ostype")) { + StreamReader sr = new StreamReader (st); + os = sr.ReadToEnd (); + } + supports_libc = os.StartsWith ("Linux"); + } catch { + } + } + + private static sock.SocketException GetException () + { + return GetException (Stdlib.GetLastError ()); + } + + private static sock.SocketException GetException (Errno error) + { + if (error == Errno.EAGAIN || + error == Errno.EWOULDBLOCK) // WSAEWOULDBLOCK + return new sock.SocketException (10035); + + if (error == Errno.EBADF || + error == Errno.ENOTSOCK) // WSAENOTSOCK + return new sock.SocketException (10038); + + if (error == Errno.ECONNABORTED) // WSAENETDOWN + return new sock.SocketException (10050); + + if (error == Errno.EINVAL) // WSAEINVAL + return new sock.SocketException (10022); + + return new sock.SocketException (); + } + } +} \ No newline at end of file diff --git a/src/Mono.WebServer.FastCgi/WebSource.cs b/src/Mono.WebServer.FastCgi/WebSource.cs new file mode 100644 index 0000000..7ef50b3 --- /dev/null +++ b/src/Mono.WebServer.FastCgi/WebSource.cs @@ -0,0 +1,65 @@ +// +// WebSource.cs: Provides a shell implementation of Mono.WebServer.WebSource +// for ApplicationServer to get the IApplicationHost type from. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; + +namespace Mono.WebServer.FastCgi { + + // FIXME: This class could be removed if + // Mono.WebServer.ApplicationServer is broken into two classes: + // * ApplicationManager to handle application hosts, and + // * ApplicationServer, a subclass of ApplicationManager that + // adds on server support. + + public class WebSource : Mono.WebServer.WebSource { + public WebSource () + { + } + + public override IRequestBroker CreateRequestBroker() + { + return null; + } + + public override System.Type GetApplicationHostType() + { + return typeof(ApplicationHost); + } + + public override Worker CreateWorker(System.Net.Sockets.Socket socket, ApplicationServer server) + { + return null; + } + + public override System.Net.Sockets.Socket CreateSocket() + { + return null; + } + } +} diff --git a/src/Mono.WebServer.FastCgi/WorkerRequest.cs b/src/Mono.WebServer.FastCgi/WorkerRequest.cs new file mode 100644 index 0000000..9d96392 --- /dev/null +++ b/src/Mono.WebServer.FastCgi/WorkerRequest.cs @@ -0,0 +1,525 @@ +// +// WorkerRequest.cs: Extends MonoWorkerRequest by getting information from and +// writing information to a Responder object. +// +// Author: +// Brian Nickel (brian.nickel@gmail.com) +// +// Copyright (C) 2007 Brian Nickel +// +// 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. +// + +using System; +#if NET_2_0 +using System.Collections.Generic; +#else +using System.Collections; +#endif +using Mono.FastCgi; +using Mono.WebServer; +using System.Text; +using System.Net; +using System.Globalization; +using System.IO; + +namespace Mono.WebServer.FastCgi +{ + public class WorkerRequest : MonoWorkerRequest + { + private static string [] indexFiles = { "index.aspx", + "default.aspx", + "index.html", + "index.htm" }; + + static WorkerRequest () + { + #if NET_2_0 + SetDefaultIndexFiles (System.Configuration.ConfigurationManager.AppSettings [ + "MonoServerDefaultIndexFiles"]); + #else + SetDefaultIndexFiles (System.Configuration.ConfigurationSettings.AppSettings [ + "MonoServerDefaultIndexFiles"]); + #endif + } + + private StringBuilder headers = new StringBuilder (); + private Responder responder; + private byte [] input_data; + private string file_path; + string raw_url = null; + private bool closed = false; + string uri_path = null; + private string [][] unknownHeaders = null; + private string [] knownHeaders = null; + + public WorkerRequest (Responder responder, ApplicationHost appHost) : base (appHost) + { + this.responder = responder; + input_data = responder.InputData; + } + + #region Overrides + + #region Overrides: Transaction Oriented + + public override int RequestId { + get {return responder.RequestID;} + } + + protected override bool GetRequestData () + { + return true; + } + + public override bool HeadersSent () + { + return headers == null; + } + + public override void FlushResponse (bool finalFlush) + { + if (finalFlush) + CloseConnection (); + } + public override void CloseConnection () + { + if (closed) + return; + + closed = true; + this.EnsureHeadersSent (); + responder.CompleteRequest (0); + } + public override void SendResponseFromMemory (byte [] data, int length) + { + EnsureHeadersSent (); + responder.SendOutput (data, length); + } + + public override void SendStatus (int statusCode, string statusDescription) + { + AppendHeaderLine ("Status: {0} {1}", + statusCode, statusDescription); + } + + public override void SendUnknownResponseHeader (string name, string value) + { + AppendHeaderLine ("{0}: {1}", name, value); + } + + public override bool IsClientConnected () + { + return responder.IsConnected; + } + + public override bool IsEntireEntityBodyIsPreloaded () + { + return true; + } + + #endregion + + #region Overrides: Request Oriented + + + public override string GetPathInfo () + { + return responder.GetParameter ("PATH_INFO"); + } + + public override string GetRawUrl () + { + if (raw_url != null) + return raw_url; + + StringBuilder b = new StringBuilder (GetUriPath ()); + string query = GetQueryString (); + if (query != null && query.Length > 0) { + b.Append ('?'); + b.Append (query); + } + + raw_url = b.ToString (); + return raw_url; + } + + + + public override bool IsSecure () + { + return responder.GetParameter ("HTTPS") == "on"; + } + + + public override string GetHttpVerbName () + { + return responder.GetParameter ("REQUEST_METHOD"); + } + + public override string GetHttpVersion () + { + return responder.GetParameter ("SERVER_PROTOCOL"); + } + + public override string GetLocalAddress () + { + string address = responder.GetParameter ("SERVER_ADDR"); + if (address != null && address.Length > 0) + return address; + + address = AddressFromHostName ( + responder.GetParameter ("HTTP_HOST")); + if (address != null && address.Length > 0) + return address; + + address = AddressFromHostName ( + responder.GetParameter ("SERVER_NAME")); + if (address != null && address.Length > 0) + return address; + + return base.GetLocalAddress (); + } + + public override int GetLocalPort () + { + try { + return responder.PortNumber; + } catch { + return base.GetLocalPort (); + } + } + + public override string GetQueryString () + { + return responder.GetParameter ("QUERY_STRING"); + } + + public override byte [] GetQueryStringRawBytes () + { + string query_string = GetQueryString (); + if (query_string == null) + return null; + return Encoding.GetBytes (query_string); + } + + public override string GetRemoteAddress () + { + string addr = responder.GetParameter ("REMOTE_ADDR"); + return addr != null && addr.Length > 0 ? + addr : base.GetRemoteAddress (); + } + + public override string GetRemoteName () + { + string ip = GetRemoteAddress (); + string name = null; + try { + #if NET_2_0 + IPHostEntry entry = Dns.GetHostEntry (ip); + #else + IPHostEntry entry = Dns.GetHostByName (ip); + #endif + name = entry.HostName; + } catch { + name = ip; + } + + return name; + } + + public override int GetRemotePort () + { + string port = responder.GetParameter ("REMOTE_PORT"); + if (port == null || port.Length == 0) + return base.GetRemotePort (); + + try { + return int.Parse (port); + } catch { + return base.GetRemotePort (); + } + } + + public override string GetServerVariable (string name) + { + string value = responder.GetParameter (name); + + if (value == null) + value = Environment.GetEnvironmentVariable (name); + + return value != null ? value : base.GetServerVariable (name); + } + + + public override string GetUriPath () + { + if (uri_path != null) + return uri_path; + + uri_path = GetFilePath () + GetPathInfo (); + return uri_path; + } + + public override string GetFilePath () + { + if (file_path != null) + return file_path; + + file_path = responder.Path; + + // The following will check if the request was made to a + // directory, and if so, if attempts to find the correct + // index file from the list. Case is ignored to improve + // Windows compatability. + + string path = responder.PhysicalPath; + + DirectoryInfo dir = new DirectoryInfo (path); + + if (!dir.Exists) + return file_path; + + if (!file_path.EndsWith ("/")) + file_path += "/"; + + FileInfo [] files = dir.GetFiles (); + + foreach (string file in indexFiles) { + foreach (FileInfo info in files) { + #if NET_2_0 + if (file.Equals (info.Name, + StringComparison.InvariantCultureIgnoreCase)) { + #else + if (file.ToLower () == info.Name.ToLower ()) { + #endif + file_path += info.Name; + return file_path; + } + } + } + + return file_path; + } + + public override string GetUnknownRequestHeader (string name) + { + foreach (string [] pair in GetUnknownRequestHeaders ()) + { + if (pair [0] == name) + return pair [1]; + } + + + return base.GetUnknownRequestHeader (name); + } + + public override string [][] GetUnknownRequestHeaders () + { + if (unknownHeaders != null) + return unknownHeaders; + + #if NET_2_0 + IDictionary pairs = + responder.GetParameters (); + #else + IDictionary pairs = responder.GetParameters (); + #endif + knownHeaders = new string [RequestHeaderMaximum]; + string [][] headers = new string [pairs.Count][]; + int count = 0; + + foreach (string key in pairs.Keys) { + if (!key.StartsWith ("HTTP_")) + continue; + + string name = ReformatHttpHeader (key); + string value = (string) pairs [key]; + int id = GetKnownRequestHeaderIndex (name); + + if (id >= 0) { + knownHeaders [id] = value; + continue; + } + + headers [count++] = new string [] {name, value}; + } + + unknownHeaders = new string [count][]; + System.Array.Copy (headers, 0, unknownHeaders, 0, count); + + return unknownHeaders; + } + + public override string GetKnownRequestHeader (int index) + { + string value = null; + switch (index) + { + case System.Web.HttpWorkerRequest.HeaderContentType: + value = responder.GetParameter ("CONTENT_TYPE"); + break; + + case System.Web.HttpWorkerRequest.HeaderContentLength: + value = responder.GetParameter ("CONTENT_LENGTH"); + break; + default: + GetUnknownRequestHeaders (); + value = knownHeaders [index]; + break; + } + + return (value != null) ? + value : base.GetKnownRequestHeader (index); + } + + public override string GetServerName () + { + string server_name = HostNameFromString ( + responder.GetParameter ("SERVER_NAME")); + + if (server_name == null) + server_name = HostNameFromString ( + responder.GetParameter ("HTTP_HOST")); + + if (server_name == null) + server_name = GetLocalAddress (); + + return server_name; + } + + public override byte [] GetPreloadedEntityBody () + { + return input_data; + } + + #endregion + + #endregion + + #region Private Methods + + private void AppendHeaderLine (string format, params object [] args) + { + if (headers == null) + return; + + headers.AppendFormat (CultureInfo.InvariantCulture, + format, args); + headers.Append ("\r\n"); + } + + private void EnsureHeadersSent () + { + if (headers != null) { + headers.Append ("\r\n"); + string str = headers.ToString (); + responder.SendOutput (str, + HeaderEncoding); + headers = null; + } + } + + #endregion + + #region Private Static Methods + + private static string AddressFromHostName (string host) + { + host = HostNameFromString (host); + + if (host == null || host.Length > 126) + return null; + + System.Net.IPAddress [] addresses = null; + try { + #if NET_2_0 + addresses = Dns.GetHostEntry (host).AddressList; + #else + addresses = Dns.GetHostByName (host).AddressList; + #endif + } catch (System.Net.Sockets.SocketException) { + return null; + } catch (ArgumentException) { + return null; + } + + if (addresses == null || addresses.Length == 0) + return null; + + return addresses [0].ToString (); + } + + private static string HostNameFromString (string host) + { + if (host == null || host.Length == 0) + return null; + + int colon_index = host.IndexOf (':'); + + if (colon_index == -1) + return host; + + if (colon_index == 0) + return null; + + return host.Substring (0, colon_index); + } + + private static string ReformatHttpHeader (string header) + { + string [] parts = header.Substring (5).Split ('_'); + for (int i = 0; i < parts.Length; i ++) + parts [i] = parts [i].Substring (0, 1).ToUpper () + + parts [i].Substring (1).ToLower (); + + return string.Join ("-", parts); + } + + private static void SetDefaultIndexFiles (string list) + { + if (list == null) + return; + + #if NET_2_0 + List files = new List (); + #else + ArrayList files = new ArrayList (); + #endif + + string [] fs = list.Split (','); + foreach (string f in fs) { + string trimmed = f.Trim (); + if (trimmed == "") + continue; + + files.Add (trimmed); + } + + #if NET_2_0 + indexFiles = files.ToArray (); + #else + indexFiles = (string []) files.ToArray (typeof (string)); + #endif + } + + #endregion + } +} diff --git a/src/Mono.WebServer.FastCgi/main.cs b/src/Mono.WebServer.FastCgi/main.cs new file mode 100644 index 0000000..b624ebe --- /dev/null +++ b/src/Mono.WebServer.FastCgi/main.cs @@ -0,0 +1,369 @@ +// +// server.cs: Web Server that uses ASP.NET hosting +// +// Authors: +// Brian Nickel (brian.nickel@gmail.com) +// Gonzalo Paniagua Javier (gonzalo@ximian.com) +// +// (C) 2002,2003 Ximian, Inc (http://www.ximian.com) +// (C) Copyright 2004 Novell, Inc. (http://www.novell.com) +// (C) Copyright 2007 Brian Nickel +// +// 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. +// + +using System; +using System.Collections.Specialized; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Reflection; +using System.Web.Hosting; +using Mono.WebServer; +using Mono.FastCgi; + +namespace Mono.WebServer.FastCgi +{ + public class Server + { + static void ShowVersion () + { + Assembly assembly = Assembly.GetExecutingAssembly (); + string version = assembly.GetName ().Version.ToString (); + object att; + + // att = assembly.GetCustomAttributes ( + // typeof (AssemblyTitleAttribute), false) [0]; + // string title = + // ((AssemblyTitleAttribute) att).Title; + + att = assembly.GetCustomAttributes ( + typeof (AssemblyCopyrightAttribute), false) [0]; + string copyright = + ((AssemblyCopyrightAttribute) att).Copyright; + + att = assembly.GetCustomAttributes ( + typeof (AssemblyDescriptionAttribute), false) [0]; + string description = + ((AssemblyDescriptionAttribute) att).Description; + + Console.WriteLine ("{0} {1}\n(c) {2}\n{3}", + Path.GetFileName (assembly.Location), version, + copyright, description); + } + + static void ShowHelp () + { + string name = Path.GetFileName ( + Assembly.GetExecutingAssembly ().Location); + + ShowVersion (); + Console.WriteLine (); + Console.WriteLine ("Usage is:\n"); + Console.WriteLine (" {0} [...]", name); + Console.WriteLine (); + configmanager.PrintHelp (); + } + + private static ApplicationServer appserver; + private static ConfigurationManager configmanager; + + public static VPathToHost GetApplicationForPath (string vhost, + int port, + string path, + string realPath) + { + return appserver.GetApplicationForPath (vhost, port, path, false); + } + + public static int Main (string [] args) + { + // Load the configuration file stored in the + // executable's resources. + configmanager = new ConfigurationManager ( + typeof (Server).Assembly, + "ConfigurationManager.xml"); + + configmanager.LoadCommandLineArgs (args); + + // Show the help and exit. + if ((bool) configmanager ["help"] || + (bool) configmanager ["?"]) { + ShowHelp (); + return 0; + } + + // Show the version and exit. + if ((bool) configmanager ["version"]) { + ShowVersion (); + return 0; + } + + try { + string config_file = (string) + configmanager ["configfile"]; + if (config_file != null) + configmanager.LoadXmlConfig ( + config_file); + } catch (ApplicationException e) { + Console.WriteLine (e.Message); + return 1; + } catch (System.Xml.XmlException e) { + Console.WriteLine ( + "Error reading XML configuration: {0}", + e.Message); + return 1; + } + + try { + string log_level = (string) + configmanager ["loglevels"]; + + if (log_level != null) + Logger.Level = (LogLevel) + Enum.Parse (typeof (LogLevel), + log_level); + } catch { + Console.WriteLine ("Failed to parse log levels."); + Console.WriteLine ("Using default levels: {0}", + Logger.Level); + } + + try { + string log_file = (string) + configmanager ["logfile"]; + + if (log_file != null) + Logger.Open (log_file); + } catch (Exception e) { + Console.WriteLine ("Error opening log file: {0}", + e.Message); + Console.WriteLine ("Events will not be logged."); + } + + Logger.WriteToConsole = (bool) configmanager ["printlog"]; + + // Send the trace to the console. + Trace.Listeners.Add ( + new TextWriterTraceListener (Console.Out)); + Console.WriteLine ( + Assembly.GetExecutingAssembly ().GetName ().Name); + + + // Create the socket. + Socket socket; + + // Socket strings are in the format + // "type[:ARG1[:ARG2[:...]]]". + string socket_type = configmanager ["socket"] as string; + if (socket_type == null) + socket_type = "pipe"; + + string [] socket_parts = socket_type.Split ( + new char [] {':'}, 3); + + switch (socket_parts [0].ToLower ()) { + case "pipe": + try { + socket = SocketFactory.CreatePipeSocket ( + IntPtr.Zero); + } catch (System.Net.Sockets.SocketException){ + Console.WriteLine ( + "Error: Pipe socket is not bound."); + return 1; + } + break; + + // The FILE sockets is of the format + // "file[:PATH]". + case "unix": + case "file": + if (socket_parts.Length == 2) + configmanager ["filename"] = + socket_parts [1]; + + string path = (string) configmanager ["filename"]; + + try { + socket = SocketFactory.CreateUnixSocket ( + path); + } catch (System.Net.Sockets.SocketException e){ + Console.WriteLine ( + "Error creating the socket: {0}", + e.Message); + return 1; + } + + Console.WriteLine ("Listening on file: {0}", + path); + break; + + // The TCP socket is of the format + // "tcp[[:ADDRESS]:PORT]". + case "tcp": + if (socket_parts.Length > 1) + configmanager ["port"] = socket_parts [ + socket_parts.Length - 1]; + + if (socket_parts.Length == 3) + configmanager ["address"] = + socket_parts [1]; + + ushort port; + try { + port = (ushort) configmanager ["port"]; + } catch (ApplicationException e) { + Console.WriteLine (e.Message); + return 1; + } + + string address_str = + (string) configmanager ["address"]; + IPAddress address; + + try { + address = IPAddress.Parse (address_str); + } catch { + Console.WriteLine ( + "Error in argument \"address\". \"{0}\" cannot be converted to an IP address.", + address_str); + return 1; + } + + try { + socket = SocketFactory.CreateTcpSocket ( + address, port); + } catch (System.Net.Sockets.SocketException e){ + Console.WriteLine ( + "Error creating the socket: {0}", + e.Message); + return 1; + } + + Console.WriteLine ("Listening on port: {0}", + address_str); + Console.WriteLine ("Listening on address: {0}", + port); + break; + + default: + Console.WriteLine ( + "Error in argument \"socket\". \"{0}\" is not a supported type. Use \"pipe\", \"tcp\" or \"unix\".", + socket_parts [0]); + return 1; + } + + string root_dir = configmanager ["root"] as string; + if (root_dir != null && root_dir.Length != 0) { + try { + Environment.CurrentDirectory = root_dir; + } catch (Exception e) { + Console.WriteLine ("Error: {0}", + e.Message); + return 1; + } + } + + root_dir = Environment.CurrentDirectory; + bool auto_map = false; //(bool) configmanager ["automappaths"]; + WebSource webSource = new WebSource (); + appserver = new ApplicationServer (webSource); + appserver.Verbose = (bool) configmanager ["verbose"]; + + string applications = (string) + configmanager ["applications"]; + string app_config_file; + string app_config_dir; + + try { + app_config_file = (string) + configmanager ["appconfigfile"]; + app_config_dir = (string) + configmanager ["appconfigdir"]; + } catch (ApplicationException e) { + Console.WriteLine (e.Message); + return 1; + } + + if (applications != null) + appserver.AddApplicationsFromCommandLine ( + applications); + + if (app_config_file != null) + appserver.AddApplicationsFromConfigFile ( + app_config_file); + + if (app_config_dir != null) + appserver.AddApplicationsFromConfigDirectory ( + app_config_dir); + + if (applications == null && app_config_dir == null && + app_config_file == null && !auto_map) { + Console.WriteLine ( + "There are no applications defined, and path mapping is disabled."); + Console.WriteLine ( + "Define an application using /applications, /appconfigfile, /appconfigdir"); + /* + Console.WriteLine ( + "or by enabling application mapping with /automappaths=True."); + */ + return 1; + } + + Console.WriteLine ("Root directory: {0}", root_dir); + Mono.FastCgi.Server server = new Mono.FastCgi.Server ( + socket); + + server.SetResponder (typeof (Responder)); + + server.MaxConnections = (ushort) + configmanager ["maxconns"]; + server.MaxRequests = (ushort) + configmanager ["maxreqs"]; + server.MultiplexConnections = (bool) + configmanager ["multiplex"]; + + Console.WriteLine ("Max connections: {0}", + server.MaxConnections); + Console.WriteLine ("Max requests: {0}", + server.MaxRequests); + Console.WriteLine ("Multiplex connections: {0}", + server.MultiplexConnections); + + bool stopable = (bool) configmanager ["stopable"]; + if (!stopable) + Console.WriteLine ( + "Use /stopable=True to enable stopping from the console."); + + server.Start (stopable); + + configmanager = null; + + if (stopable) { + Console.WriteLine ( + "Hit Return to stop the server."); + Console.ReadLine (); + server.Stop (); + } + + return 0; + } + } +} diff --git a/src/AssemblyInfo.cs.in b/src/Mono.WebServer.XSP/AssemblyInfo.cs.in similarity index 88% rename from src/AssemblyInfo.cs.in rename to src/Mono.WebServer.XSP/AssemblyInfo.cs.in index 46fa0ec..c9a4942 100644 --- a/src/AssemblyInfo.cs.in +++ b/src/Mono.WebServer.XSP/AssemblyInfo.cs.in @@ -3,7 +3,7 @@ // Authors: // Gonzalo Paniagua Javier (gonzalo@ximian.com) // -// Copyright (c) 2002,2003,2004,2005,2006 Novell, Inc. (http://www.novell.com) +// Copyright (c) 2002-2007 Novell, Inc. (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -29,9 +29,9 @@ using System.Reflection; using System.Runtime.CompilerServices; [assembly: AssemblyVersion("@XSP_VERSION@")] -[assembly: AssemblyTitle ("Mono-XSP Server")] +[assembly: AssemblyTitle ("Mono.WebServer.XSP")] [assembly: AssemblyDescription ("Minimalistic web server for testing System.Web")] -[assembly: AssemblyCopyright ("(c) 2002-2006 Novell, Inc.")] +[assembly: AssemblyCopyright ("(c) 2002-2007 Novell, Inc.")] [assembly: AssemblyCompany ("Novell, Inc.")] [assembly: AssemblyDelaySign(true)] [assembly: AssemblyKeyFile("@top_srcdir@/src/mono.pub")] diff --git a/src/Mono.WebServer.XSP/Makefile.am b/src/Mono.WebServer.XSP/Makefile.am new file mode 100644 index 0000000..3210914 --- /dev/null +++ b/src/Mono.WebServer.XSP/Makefile.am @@ -0,0 +1,44 @@ +builddir=$(top_builddir)/src/Mono.WebServer.XSP + +MCSFLAGS= -debug+ -debug:full -nologo -nowarn:618 $(WEBTRACING) + +GACUTIL1=$(GACUTIL) -package 1.0 + +if NET_2_0 +XSP2_EXE = xsp2.exe +GACUTIL2=$(GACUTIL) -package 2.0 +endif + +noinst_SCRIPTS=xsp.exe xsp.exe $(XSP2_EXE) + +CLEANFILES = *.exe *.mdb + +# +references = -r:System.Web.dll -r:../Mono.WebServer/Mono.WebServer.dll -r:Mono.Security.dll +references2 = -r:System.Web.dll -r:System.Configuration.dll -r:../Mono.WebServer/Mono.WebServer2.dll -r:Mono.Security.dll + +sources = main.cs SecurityConfiguration.cs + +build_sources = $(addprefix $(srcdir)/, $(sources)) AssemblyInfo.cs + +EXTRA_DIST = $(sources) AssemblyInfo.cs.in + +xsp.exe: $(build_sources) + $(MCS) $(MCSFLAGS) $(references) /out:$@ $(build_sources) + $(SN) -q -R $(builddir)/$@ $(srcdir)/../mono.snk + +xsp2.exe: $(build_sources) + $(GMCS) -d:NET_2_0 $(MCSFLAGS) $(references2) /out:$@ $(build_sources) + $(SN) -q -R $(builddir)/$@ $(srcdir)/../mono.snk + +install-data-local: + $(GACUTIL1) $(GACUTIL_FLAGS) -i $(builddir)/xsp.exe + +#if NET_2_0 + $(GACUTIL2) $(GACUTIL_FLAGS) -i $(builddir)/xsp2.exe +#endif + +uninstall-local: + -for i in xsp xsp2 ; do \ + $(GACUTIL) $(GACUTIL_FLAGS) -u $$(basename $$i .exe) ; \ + done diff --git a/src/security.cs b/src/Mono.WebServer.XSP/SecurityConfiguration.cs similarity index 98% rename from src/security.cs rename to src/Mono.WebServer.XSP/SecurityConfiguration.cs index 42db291..7f444a7 100644 --- a/src/security.cs +++ b/src/Mono.WebServer.XSP/SecurityConfiguration.cs @@ -28,7 +28,6 @@ // using System; -#if !MODMONO_SERVER using System.Collections; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; @@ -39,21 +38,10 @@ using Mono.Security.Protocol.Tls; using MSX = Mono.Security.X509; using Mono.Security.X509.Extensions; using SecurityProtocolType = Mono.Security.Protocol.Tls.SecurityProtocolType; -#endif -namespace Mono.XSP { +namespace Mono.WebServer.XSP { public class SecurityConfiguration { -#if MODMONO_SERVER - public bool Enabled { - get { return false; } - } - - public override string ToString () - { - return String.Empty; - } -#else private bool enabled; private bool valid; private bool accept_client_certificates; @@ -393,6 +381,5 @@ namespace Mono.XSP { IDictionary attributes = pfx.GetAttributes (cert); return (pfx.GetAsymmetricAlgorithm (attributes) as RSA); } -#endif } } diff --git a/src/server.cs b/src/Mono.WebServer.XSP/main.cs similarity index 81% rename from src/server.cs rename to src/Mono.WebServer.XSP/main.cs index 775c08e..7d913e4 100644 --- a/src/server.cs +++ b/src/Mono.WebServer.XSP/main.cs @@ -1,11 +1,11 @@ // -// server.cs: Web Server that uses ASP.NET hosting +// Mono.WebServer.XSP/main.cs: Web Server that uses ASP.NET hosting // // Authors: // Gonzalo Paniagua Javier (gonzalo@ximian.com) // // (C) 2002,2003 Ximian, Inc (http://www.ximian.com) -// (C) Copyright 2004 Novell, Inc. (http://www.novell.com) +// (C) Copyright 2004-2007 Novell, Inc. (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -37,18 +37,14 @@ using System.Reflection; using System.Web.Hosting; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -#if !MODMONO_SERVER using Mono.Security.Protocol.Tls; -#endif using Mono.WebServer; -namespace Mono.XSP +namespace Mono.WebServer.XSP { public class Server { -#if !MODMONO_SERVER static RSA key; -#endif static void ShowVersion () { @@ -66,40 +62,19 @@ namespace Mono.XSP static void ShowHelp () { -#if MODMONO_SERVER - Console.WriteLine ("mod-mono-server.exe is a ASP.NET server used from mod_mono."); - Console.WriteLine ("Usage is:\n"); - Console.WriteLine (" mod-mono-server.exe [...]"); - Console.WriteLine (); - Console.WriteLine (" The arguments --filename and --port are mutually exlusive."); - Console.WriteLine (" --filename file: a unix socket filename to listen on."); - Console.WriteLine (" Default value: /tmp/mod_mono_server"); - Console.WriteLine (" AppSettings key name: MonoUnixSocket"); - Console.WriteLine (); -#else Console.WriteLine ("XSP server is a sample server that hosts the ASP.NET runtime in a"); Console.WriteLine ("minimalistic HTTP server\n"); Console.WriteLine ("Usage is:\n"); Console.WriteLine (" xsp.exe [...]"); Console.WriteLine (); -#endif Console.WriteLine (" --port N: n is the tcp port to listen on."); -#if MODMONO_SERVER - Console.WriteLine (" Default value: none"); -#else Console.WriteLine (" Default value: 8080"); -#endif Console.WriteLine (" AppSettings key name: MonoServerPort"); Console.WriteLine (); Console.WriteLine (" --address addr: addr is the ip address to listen on."); -#if MODMONO_SERVER - Console.WriteLine (" Default value: 127.0.0.1"); -#else Console.WriteLine (" Default value: 0.0.0.0"); -#endif Console.WriteLine (" AppSettings key name: MonoServerAddress"); Console.WriteLine (); -#if !MODMONO_SERVER Console.WriteLine (" --https: enable SSL for the server"); Console.WriteLine (" Default value: false."); Console.WriteLine (" AppSettings key name: "); @@ -129,7 +104,6 @@ namespace Mono.XSP Console.WriteLine (" Default value: Default (all)"); Console.WriteLine (" AppSettings key name: "); Console.WriteLine (); -#endif Console.WriteLine (" --root rootdir: the server changes to this directory before"); Console.WriteLine (" anything else."); Console.WriteLine (" Default value: current directory."); @@ -168,14 +142,6 @@ namespace Mono.XSP Console.WriteLine (" Default value: /:."); Console.WriteLine (" AppSettings key name: MonoApplications"); Console.WriteLine (); -#if MODMONO_SERVER - Console.WriteLine (" --terminate: gracefully terminates a running mod-mono-server instance."); - Console.WriteLine (" All other options but --filename or --address and --port"); - Console.WriteLine (" are ignored if this option is provided."); - Console.WriteLine (" --master: this instance will be used to by mod_mono to create ASP.NET"); - Console.WriteLine (" applications on demand. If this option is provided, there is no"); - Console.WriteLine (" need to provide a list of applications to start."); -#endif Console.WriteLine (" --nonstop: don't stop the server by pressing enter. Must be used"); Console.WriteLine (" when the server has no controlling terminal."); Console.WriteLine (); @@ -220,12 +186,10 @@ namespace Mono.XSP } } -#if !MODMONO_SERVER static AsymmetricAlgorithm GetPrivateKey (X509Certificate certificate, string targetHost) { return key; } -#endif static NameValueCollection AppSettings { get { @@ -261,9 +225,7 @@ namespace Mono.XSP object oport; string ip = AppSettings ["MonoServerAddress"]; bool master = false; -#if MODMONO_SERVER - string filename = AppSettings ["MonoUnixSocket"]; -#endif + if (ip == "" || ip == null) ip = "0.0.0.0"; @@ -279,19 +241,6 @@ namespace Mono.XSP hash ^= args [idx].GetHashCode () + i; switch (a){ -#if MODMONO_SERVER - case "--filename": - CheckAndSetOptions (a, Options.FileName, ref options); - filename = args [++i]; - break; - case "--terminate": - CheckAndSetOptions (a, Options.Terminate, ref options); - break; - case "--master": - CheckAndSetOptions (a, Options.Master, ref options); - master = true; - break; -#else case "--https": CheckAndSetOptions (a, Options.Https, ref options); security.Enabled = true; @@ -323,7 +272,6 @@ namespace Mono.XSP case "--protocols": security.SetProtocol (args [++i]); break; -#endif case "--port": CheckAndSetOptions (a, Options.Port, ref options); oport = args [++i]; @@ -367,28 +315,6 @@ namespace Mono.XSP } } -#if MODMONO_SERVER - if (hash < 0) - hash = -hash; - - string lockfile; - bool useTCP = ((options & Options.Port) != 0); - if (!useTCP) { - if (filename == null || filename == "") - filename = "/tmp/mod_mono_server"; - - if ((options & Options.Address) != 0) { - ShowHelp (); - Console.WriteLine (); - Console.WriteLine ("ERROR: --address without --port"); - Environment.Exit (1); - } lockfile = Path.Combine (Path.GetTempPath (), Path.GetFileName (filename)); - lockfile = String.Format ("{0}_{1}", lockfile, hash); - } else { - lockfile = Path.Combine (Path.GetTempPath (), "mod_mono_TCP_"); - lockfile = String.Format ("{0}_{1}", lockfile, hash); - } -#endif IPAddress ipaddr = null; ushort port; try { @@ -417,24 +343,6 @@ namespace Mono.XSP rootDir = Directory.GetCurrentDirectory (); WebSource webSource; -#if MODMONO_SERVER - if (useTCP) { - webSource = new ModMonoTCPWebSource (ipaddr, port, lockfile); - } else { - webSource = new ModMonoWebSource (filename, lockfile); - } - - if ((options & Options.Terminate) != 0) { - if (verbose) - Console.WriteLine ("Shutting down running mod-mono-server..."); - - bool res = ((ModMonoWebSource) webSource).GracefulShutdown (); - if (verbose) - Console.WriteLine (res ? "Done." : "Failed"); - - return (res) ? 0 : 1; - } -#else if (security.Enabled) { try { key = security.KeyPair; @@ -449,7 +357,7 @@ namespace Mono.XSP } else { webSource = new XSPWebSource (ipaddr, port); } -#endif + ApplicationServer server = new ApplicationServer (webSource); server.Verbose = verbose; @@ -465,15 +373,9 @@ namespace Mono.XSP if (!master && apps == null && appConfigDir == null && appConfigFile == null) server.AddApplicationsFromCommandLine ("/:."); -#if MODMONO_SERVER - if (!useTCP) { - Console.WriteLine ("Listening on: {0}", filename); - } else -#endif - { + Console.WriteLine ("Listening on port: {0} {1}", port, security); Console.WriteLine ("Listening on address: {0}", ip); - } Console.WriteLine ("Root directory: {0}", rootDir); diff --git a/src/TODO b/src/TODO new file mode 100644 index 0000000..8b8ea98 --- /dev/null +++ b/src/TODO @@ -0,0 +1,14 @@ + +* Move Mono.WebServer.FastCgi/CondifgurationManager.cs + to Mono.WebServer. + +* Fix Mono.WebServer.FastCgi/ApplicationManager.cs' automapping + feature and move it to Mono.WebServer. + +* Write unit tests for ApplicationManager.cs' automapping. + +* Reuse the Configuration- and ApplicatioManager in + Mono.WebServer.Apache and .XSP. + +* Write fastcgi-mono-server(1) (partly done), import Brian's docs. + diff --git a/src/Tracing.cs b/src/Tracing.cs deleted file mode 100644 index 5c468c7..0000000 --- a/src/Tracing.cs +++ /dev/null @@ -1,117 +0,0 @@ -// -// Copied from System.Web.Util.WebTrace and changed a few lines -// Under MS runtime, Trace does not work because System.Web disables normal -// tracing. -// -// Authors: -// Gonzalo Paniagua Javier (gonzalo@ximian.com) -// -// (C) 2002 Ximian, Inc (http://www.ximian.com) -// - -using System; -using System.Collections; -using System.Diagnostics; - -namespace Mono.WebServer -{ - internal class WebTrace - { - static Stack ctxStack; - static bool trace; - static int indentation; // Number of \t - - static WebTrace () - { - ctxStack = new Stack (); - } - - [Conditional("WEBTRACE")] - static public void PushContext (string context) - { - ctxStack.Push (context); - indentation++; - } - - [Conditional("WEBTRACE")] - static public void PopContext () - { - if (ctxStack.Count == 0) - return; - - indentation--; - ctxStack.Pop (); - } - - static public string Context - { - get { - if (ctxStack.Count == 0) - return String.Empty; - - return (string) ctxStack.Peek (); - } - } - - static public bool StackTrace - { - get { return trace; } - - set { trace = value; } - } - - [Conditional("WEBTRACE")] - static public void WriteLine (string msg) - { - Console.WriteLine (Format (msg)); - } - - [Conditional("WEBTRACE")] - static public void WriteLine (string msg, object arg) - { - Console.WriteLine (Format (String.Format (msg, arg))); - } - - [Conditional("WEBTRACE")] - static public void WriteLine (string msg, object arg1, object arg2) - { - Console.WriteLine (Format (String.Format (msg, arg1, arg2))); - } - - [Conditional("WEBTRACE")] - static public void WriteLine (string msg, object arg1, object arg2, object arg3) - { - Console.WriteLine (Format (String.Format (msg, arg1, arg2, arg3))); - } - - [Conditional("WEBTRACE")] - static public void WriteLine (string msg, params object [] args) - { - Console.WriteLine (Format (String.Format (msg, args))); - } - - static string Tabs - { - get { - if (indentation == 0) - return String.Empty; - - return new String ('\t', indentation); - } - } - - static string Format (string msg) - { - string ctx = Tabs + Context; - if (ctx.Length != 0) - ctx += ": "; - - string result = ctx + msg; - if (trace) - result += "\n" + Environment.StackTrace; - - return result; - } - } -} -