Creating A WebApplication
WebApplications are very usefull for adding online administration to your mod. You could also use a WebApplication to display information about the server.
This document will describe how you can create your own WebApplication. This document is focused on UT2003, so something will not work the same if you use it for a other version.
Setup
Configuration
[UWeb.WebServer] Applications[0]=xWebAdmin.UTServerAdmin ApplicationPaths[0]=/ServerAdmin Applications[1]=xWebAdmin.UTImageServer ApplicationPaths[1]=/images Applications[2]=MyMod.MyWebAdmin ApplicationPaths[2]=/MyWebAdmin Applications[3]=MyMod.MyImageServer ApplicationPaths[3]=/MyImages DefaultApplication=0 bEnabled=True ListenPort=80 MaxConnections=30 ServerName=myserver.com ExpirationSeconds=86400
In order to use your WebApplication you have to add it to the Applications list of the UWeb.WebServer. You can add up to 10 WebApplications. You also have to define the ApplicationPaths for your WebApplication. The ApplicationPath defines what WebApplication that should handle the request.
For example, when the request URL is:
http://myserver.com/ServerAdmin/somepage
It will forward the request to the WebApplication with it's path set to: /ServerAdmin
In this case that would be xWebAdmin.UTServerAdmin
If there's no Application assigned to the requested path it will load the default application.
Files
There are two ways to provide the data for the resulting pages.
The first method is to provide the output HTML inline with the code. This is a bad choice because it's ugly and difficult to maintain. It will also use more memory because the source code is much larger.
The second method is to provide the HTML (and images) as loose files in a directory.
All Web files are provided from the "Web" dir in your Unreal directory, when you include a file in the respons the location specified will be relative from this directory. It's best that you create a new subdirectory for your own WebApplication.
So your directory listing will look like this:
UT2003 | +- Web | +- images +- ServerAdmin +- MyWebAdmin (place your HTML and CSS files here) +- MyImages (for your graphics, if you use your own ImageServer)
For the rest of this document we assume we have the following files in the MyWebAdmin directory:
- index.html (the main document)
- about.html (about document)
And in the MyImages directory:
- title.gif
WebApplication class
TODO: note about Modular Mod
class MyWebAdmin extends WebApplication; var localized string url_root; var localized string file_default; var localized string file_about;
Create a class that extends a WebApplication.
I've specified all files that this WebApplication serves as a localisable variable. This is usefull because it will prevents stupid typos in the filename, but also provides you with an easy way to localise your WebApplication. For an other language you just specify an other filename to use.
event Init() { // do some init stuff Super.Init(); log("MyWebAdmin loaded"); }
The Init method is called when the WebApplication will be loaded. If you need to load some things, or find some references (find a mutator or something), you should do it here.
event bool PreQuery(WebRequest Request, WebResponse Response) { if (!Level.Game.AccessControl.ValidLogin(Request.Username, Request.Password)) { Response.FailAuthentication(Level.Game.GameReplicationInfo.ServerName$" MyWebAdmin"); return false; } return true; }
PreQuery is called before the Query is handled. If your WebApplication needs some authentication you should do it here.
In the above example I call the ValidLogin method of AccessControl, this will validate the username and password entered in the browser. If AccessControlIni is used it will check for a valid user/pass combination, otherwise it will just check if the adminpassword is correct.
When the username and password are correct it will return true and the WebApplication will continue with the Query event. Otherwise false is returned and query processes stops, a FailAuthentication is sent to the browser requiring the user to login again.
function Query(WebRequest Request, WebResponse Response) { switch (Mid(Request.URI, 1)) { case "": case file_default: RequestIndex(Request, Response); return; case file_about: RequestAbout(Request, Response); return; } Response.HTTPError(404, ""); }
Query event is called when a user requests a file. The Request.URI value is relative to the Application path defined for this application.
For example, when a user requests the following page:
http://myserver.com/ServerAdmin/index.html
Request.URI will contain the following value:
/index.html
We strip off the leading slash to check what file is requested. If the string is empty or same as the variable file_default we will call the RequestIndex method to handle.
If none of the cases match we will return a 404 HTTP Error.
function bool RequestIndex(WebRequest Request, WebResponse Response) { Response.Subst("replace", "withthis"); Response.IncludeUHTM(url_root$"/"$file_default); Response.ClearSubst(); return true; }
The above method is called by Query when the client requests either "/" or "/index.html" (file_default = "index.html").
Response.Subst will replace all occurances of "%replace%" in the file to "withthis". Response.IncludeUHTM will include the HTML file to send. Response.ClearSubst will perform the substitutions in the included file.
Let's say that our index.html file looks like this:
<html> <head> <title>MyWebAdmin</title> </head> <body> <img src="/MyImages/title.gif" alt="title image> The replace item is replaced with: %replace% <br> <a href="about.html">about this thing</a> </body> </html>
The file returned by the server will look like this:
<html> <head> <title>MyWebAdmin</title> </head> <body> <img src="/MyImages/title.gif" alt="title image> The replace item is replaced with: withthis <br> <a href="about.html">about this thing</a> </body> </html>
Note: I've added a image to the index.html doc, it refers to /MyImages/title.gif , if you do not provide your own ImageServer (this is usualy not very usefull to do) you should place your pictures in the UWeb/images dir and use /images/title.gif as image source.
function PostQuery(WebRequest Request, WebResponse Response) { // do some cleanup here }
PostQuery is called after the the query was executed, you may want to do some clean up here.
defaultproperties { url_root="MyWebAdmin" file_default="index.html" file_about="about.html" }
Well these are the default values of the variable described above. If you have localized html files you could overwrite these within in .int file, so for file_default you could set it to: index-de.html (for german files) and index-fr.html (for french files)
MyImageServer class (optional)
class MyImageServer extends ImageServer; var string imagedir; event Query(WebRequest Request, WebResponse Response) { local string Image; Image = Mid(Request.URI, 1); if( Right(Caps(Image), 4) == ".JPG" || Right(Caps(Image), 5) == ".JPEG" ) { Response.SendStandardHeaders("image/jpeg", true); } else if( Right(Caps(Image), 4) == ".GIF" ) { Response.SendStandardHeaders("image/gif", true); } else if( Right(Caps(Image), 4) == ".BMP" ) { Response.SendStandardHeaders("image/bmp", true); } else if( Right(Caps(Image), 4) == ".PNG" ) { Response.SendStandardHeaders("image/png", true); } else { Response.HTTPError(404); return; } Response.IncludeBinaryFile( imagedir$"/"$Image ); } defaultproperties { imagedir="MyImages" }
Providing your own ImageServer is optional, and you should only do that if you provide a pretty large WebApplication with a lot of images.
The only thing diffirent from the default ImageServer and the above code is that this image server will server the files from a diffirent directory (Web/MyImages) and I've added an extra mime type: "image/png"
Differences between UT2003 and Devastation
There are a few differences between the code described above and that needed by Devastation. These are detailed below. From the looks of the code the Devastation codebase is much closer to that of UT so these comments may apply to UT as well.
All files served by the Web Server application must be contained within the /Web directory. Files in sub-directories (or any filename containing a forwardslash or backslash will not be served. Instead you will see a Dangerous characters in requested filename error in your log.
The version of the engine used by Devastation does not support the event bool PreQuery(WebRequest Request, WebResponse Response) method within UScript (the function is defined as native). This means that a different approach needs to be taken for user authnentication. The code required is below.
// Add the following variables as class attributes. var config String AdminRealm; var config String AdminUsername; var config String AdminPassword; // Add the following code at the start of your Query(WebRequest Request, WebResponse Response) function. // Check authentication: if ((AdminUsername != "" && Caps(Request.Username) != Caps(AdminUsername)) || (AdminPassword != "" && Caps(Request.Password) != Caps(AdminPassword))) { Response.FailAuthentication(AdminRealm); return; }
Another method of nationalization
If you do not wish to store all of your pages in localized variables then an alternative is to prefix all of your files with the language identifier and an underscore.
Declare attributes on your class as follows:
var localized String LangPrefix; // This should be set to the language prefix (I use eng for English) var String pageDirectory;
Then add the following line of code to your Web Application's Init() function:
self.pageDirectory = self.root_url$"/"$self.LangPrefix$"_";
The code to determine which page has been requested then becomes the following:
function Query(WebRequest Request, WebResponse Response) { local int underscorePos; local String requestedPage; underscorePos = InStr( Request.URI, "_" ); if (underscorePos == -1 && Len(Request.URI) > 1) { Log( "WebAdmin:: Bad URL requested - it has no underscore. URL="$Request.URI ); } else { requestedPage = Mid(Request.URI, underscorePos+1); Response.Subst( "AdminDir", self.pageDirectory ); // Replacement for page locations done here as it applies to all pages Log( "DevWebAdmin:: Requested page is "$requestedPage ); switch ( requestedPage ) { case "/": case "index.html": RequestIndex(Request, Response); return; // Rest of code follows as expected
And finally, when declareing URLs within your html file you should use the following notation:
<a href="%AdminDir%index.html">Home</a>
Related Topics
EntropicLqd: Tarquin - I'm thinking of writing a tutorial for a fully working example based on the Devastation engine. Some of the content will obviously overlap the stuff on this page (I may be able to create the pages such that I avoid some duplication, but only by moving a fair amonut of content around) - any thoughts? Also, I'll be wanting to include the contents of the HTML pages used in the app. so I'll want to display the raw html. How do I do that using Wiki Markup. I can't see anything obvious on the Wiki Markup page (although it's quite possible I'm being particularly dense).
Tarquin: Hmm... method 1: <br> method 2:
<br>
I know naaaaaathing (in the style of Manuel) about this topic, so do whatever you think is best
EntropicLqd: Cool. I'll get on it. If I can send the kids outside to playin the street with cars over the weekend I might get it done this weekend - if not middle of next week. Considering you know nothing about it, the page is amazingly comprehensive. RE: The markup - I was being particuarly dense.
Tarquin: I didn't write any of it... Is the page you mean Devastation Dedicated Server? There's Running A Dedicated Server With UCC too. If all these are related can we try & structure it around a hub page of some sort, eg [Dedicated Server]??
EntropicLqd: I'm sure it's related. And yes, a dedicated server page would probable make a lot of sense. Has anyone found a way of getting more than 24 hours into the day yet? I think I need to know how. So that's two areas on my work stack - this one (Web admin) and a dedicated server page. How nice.
Tarquin: .... erm... just leave it for someone else to do, Ent! Don't stress it! Category To Do