The attached project contains a demonstration program using Gossamer, a web server in a UserControl.
Adding the control to your projects is simple. To use it just drop an instance onto a Form, set a few properties, and optionally write two event handlers. Then call StartListening and Gossamer does the rest.
Gossamer handles:
Static GET requests.
Dynamic GET requests.
Static HEAD requests.
Dynamic POST requests.
Dynamic requests are processed by raising an event back to Gossamer's container Form, where your logic processes the request and returns results.
A tiny sample web site is included that the example program works with to show how to use Gossamer. It has an example of handling dynamic requests as well as static ones.
It would be easy to repackage Gossamer as an OCX if desired.
Gossamer is probably not fully debugged, having been thrown together in two days. But I believe I have gotten most of the glaring bugs out. You can easily add more logic to extend its capabilities as well as tweak things and fix problems.
Testing the sample is easy. Just unzip the project into a folder, open GossDemo1.vbp, run the program and click its Start button. Then point a browser to localhost:8080 to view the example web site.
Several small improvements for stability and performance, added member descriptions and marked class members intended for internal use as "hidden." See comments at head of modules.
Changes impacting use include:
VDir property can no longer be changed while Gossamer is listening.
Relative paths may now be used in hrefs and requests as long as they don't try to escape VDir.
Ok, one last update for now. Remember, you can comment here as well as sending PMs.
Unless there are bugs to fix I don't plan to post further updates. The coding was kept fairly simple so you can make changes on your own. That webcam server was a nice thing one person made, and the mini-forum was interesting but I'd probably just use a stand alone web server product for that myself.
No, I was not planning to add HTTPS support.
This version adds support for browser caching via the If-Modified-Since header. It also exposes a few new Gossamer properties and methods of use in programming dynamic request handlers. I hope it doesn't add any new bugs.
Corrected an absurd, small, non-fatal mistake in handling I/O buffering of static resources (files). Increased buffer size somewhat for improved performance. Now a Const, this buffer size could easily be made a Gossamer property if desired instead.
As written, Gossamer raises an event to the parent program upon receiving any POST request or any GET request that has parameters.
This isn't entirely kosher, because the usual approach is to define VDirs for active content (i.e. CGI-BIN folders, etc.) or otherwise mark those incoming URIs requiring dynamic processing. This could be changed if required of course.
In any case, when Gossamer sees a dynamic GET/POST request it raises that event to its container, generally a Form. The event handler has a raft of parameters containing the inputs from the request and accepts outputs to make up a response to go back.
What you do in the event handler and container is up to you. The Form supplied in the archive(s) above is just a trivial example.
However...
Gossamer is not meant to replace general purpose Web servers. It was designed as an embedded server within a VB6 program. As such, the idea was that you would write all of the server-side logic required in VB6.
Yes, it is possible to call PHP either through its CGI interface, or its ISAPI interface (I think it has one), or maybe even the Apache module API. My guess is that this would take some work though. Even the simple CGI interface can get messy, since you'll have to pack up a lot of things into a private Environment Variables block before calling CreateProcess() on the PHP interpreter.
How would you change the structure and use html code inside the program like
strHTLM = "<HTML><BODY><H1>" & SomeText & "</H1></BODY></HTML>"
and not use the site with htm files?
Look in the client connection handling UserControl (GossClient). When a GET or HEAD request is found, the presence of request query parameters (beginning with a "?") currently is taken as a request for dynamic content instead of a static resource.
This bit of logic could be changed to treat other indications as a request to be processed dynamically. For example you might look at strParts(1) in this part of the code, and if certain values are found that might normally be seen as a request for a file you could call Parent.RaiseDynamicRequest.
So a URL like http://localhost/status.z might be taken as a request for a dynamic "status" report, or http://localhost/snapshot.z might be a request to snap a webcam picture and return it, etc. The key here would be anything requested that ends in a .z extension.
It has changes in GossClient to change the behavior of Gossamer, and in Form1 to take advantage of the changes to GossClient.
This version treats any GET request for a static page as a special dynamic request and creates the page to return from inside the program. It doesn't use any .HTM files.
dilettante thank you for taking the time for this hack, it working great with single output but can it still have images and get and post feature and selective output html code like link to other html output? I tried to put <img src=""images.jpg""> inside the html code and image in same folder as the .exe but it does not display image on the website, what would be the trick there? How would you grab the value from post and get from webpage with out having it in the url?
When your web server gets a request for a resource it is supposed to respond with the proper response.
You need to look at what is requested and decide what goes back to the browser. If they ask for "images.jpg" and you want that to return an image, you detect it in the request and respond with the right headers and image data.
The hacked version treats ALL requests as dynamic requests. It will never retrieve and send a file.
You might be better off going back to the original version, and then change it so that it treats requests with a certain resource extension as a dynamic request and others as static requests. Just pick something fairly typical (.CGI, .ASP, .DYN) and use that.
The original version treats any GET request with parameters and any POST request as dynamic, i.e.:
GET /anything.xxx?a&b=c
You'll want to change it so:
GET /folder/resource.dyn
GET /folder/other.dyn?a&b=c
... are treated as dynamic requests. Then when a static request comes in like:
GET /folder/image.jpg
... it still gets treated as a static request, and Gossamer will fetch it from the appropriate disk file.
You could also trigger on a folder as well:
GET /dyn/resource.htm
GET /folder/other.dyn?a&b=c
As written the non-hacked version looks for static resources inside a site folder next to the EXE. You can change this by changing your Gossamer control's VDir property to something else, either at design time or run time.
Last edited by dilettante; Mar 29th, 2009 at 09:09 PM.
Public Function ProcessPHP(ByVal FilePath As String, Optional ByVal phpPath As String = "C:\XAMPP\php\php-win.exe", Optional ByVal Args As String = "-f") As String
'Using reference to: Windows Script Host Object Model
' "System32\wshom.ocx"
'/// Declare Shell Objects
Dim Shell As New WshShell
Dim retObj As WshExec
Dim retStr As TextStream
'/// Declare Basic Variables
Dim cCommand As String
Dim q As String
'/// q is for quoting the filepath correctly in the command
q = """"
'/// Put together the command
cCommand = phpPath & " " & Args & " " & q & FilePath & q
'/// Execute command
Set retObj = Shell.Exec(cCommand)
'/// Put return value in textstream and make a string value out of it
Set retStr = retObj.StdOut
ProcessPHP = retStr.ReadAll
End Function
Basically this is it.
But you have to set reference to the "Windows Script Host Object Model" and you have to modify the path to the php executable.
The current is the path to a xampp installation.
All that's left to implement this is constructing a file extension handler (if there isn't one yet in this app) and passing the filepath through this function. The result will be the generated HTML.
While you can do anything you want with it, Gossamer was not primarily intended to be used to create a general purpose web server. Its real purpose is to write VB6 programs that are servers, and happen to use HTTP as the client connection protocol.
The demo doesn't reflect this since it was meant as a quick and dirty example of use. So as such it was designed for web browsers as clients even though this normally isn't what you'd be doing. As I said, you certainly can use Gossamer to make a web server, but then why would you want it to support PHP, ASP, CGI, etc? The whole reason for Gossamer is for a VB6 program to do the things you might otherwise be forced to do in a scripting language.
Secondarily it can be used to provide a web UI to a VB6 program. That's more in line with what the demo does.
But the sweet spot is to let you write VB6 programs that provide web services.
This has become much easier now that even Microsoft is backing away from SOAP as a bad approach. It adds unneeded complexity, interoperability challenges, and high costs in maintenance over an application's lifetime.
Instead we see more REST-ful protocols used on top of HTTP now. These may use XML, they may use JSON, or they may use another serialization format. But they're all easier to use and bypass many of the woes of the SOAP era.
In any case, after a long while I have a new version to post.
This one has added a new "soft Winsock error" event type and handles these more gracefully. Right now the only error identified as "soft" and ignorable is sckWouldBlock, which could occur on the listening socket under certain circumstances and can be ignored.
The code in the demo Form also has a small change to accomodate this and to fix a silly bug that cause the state of the Start/Stop buttons to change when it shouldn't.
Last edited by dilettante; Oct 15th, 2013 at 10:56 PM.
Corrected one serious, long-standing flaw. Corrected a few small flaws as well as some quite minor or cosmetic things.
The main bug fixed here is the way the HTTP response line was being built. There was no space being emitted between the status code and status text, and this error has been there since the beginning. How the program ever worked is a mystery!
The other bug of consequence that was fixed is to send a Content-Length header of 0 on responses with no body following the response headers. This is another issue that should have resulted in client hangs from time to time.
All of this just confirms that whenever possible you want to use existing well tested software when you can instead of trying to roll your own solution.
Sadly for us there isn't any readily available low cost HTTP server component for use in VB6 programs, and even fewer where the source code is available. As I find and fix things I'll try to keep updating. I do try to rerun regression tests to catch anything that used to work but gets broken by updates.
A Known Limitation
Gossamer 1.x is not designed to handle uploading binary data. It can also fail in some cases if a client is sending UTF-8 encoded requests instead of 7-bit ASCII.
I hope to produce a version 2.0 in the future that addresses this, but for now keep these issues in mind.
Hi, this web server is great! Apart from localhost, would you teach me what shall I do to open the server for Internet access?
That is more of an administration issue than a programming issue.
While there are threads here in the CodeBank on ways to automate creation of NAT router port fowarding and firewall rules these are a limited solution at best. There are lots of routers and lots of firewalls in use that cannot be controlled in such a manner.
Gossamer and similar components are best used within LANs as an alternate user interface for long-running programs such as those built as Windows Services. They aren't as useful exposed to the public Internet and are not meant as alternatives to normal web servers for general use.
Interesting Dillettante, I will investigate the way to communicate using your web server, and an Android App using B4A to communicate in both way.
The problem is that it needs to rediret the port on the router to the local computer on the network
I am still investigating, but great job.
The problem is that it needs to rediret the port on the router to the local computer on the network
This is a "problem" for all webservers. It is normally quite easy to edit the firewall rules so it should only take you a few minutes with google to figure it out.
Edit: Also agree with other that this a great project
upnp has a reputation for being insecure (rightly or wrongly) and many businesses simply turn it off as a best practice. The last few "residential" routers i bought also had it off by default too.
I converted Gossamer to utilize SimpleServer instead of the Winsock Control. It utilizes a single User Control, as mServer(0) serves as the listening socket and the remainder of the socket arrays service the connections. It provides the ability to use IPV6 instead of just IPV4, although this has not been tested as yet.
I tried to preserve as much of the original code as I could. It is not necessarily the way that I would normally do it. Each programmer has his/her own style. For example, the original code used STATIC_BUFFER_SZ As Long = 8192. SimpleServer uses whatever the operating system provides. In my case the buffer size is 65,536, and for the moment I have left the buffer size at 8,192 until I can figure out how to transfer that value from SimpleServer.
There is no reason that encrypted data cannot be integrated into this service. If I was to do it, I would incorporate TLS 1.3. This can be viewed as a starting point, as there is only a small part of TLS 1.2 that provides the same protection. All previous versions are obsolete/obfuscated.
This download does not include the contents of the \site or \art directories.
J.A. Coutts
Updated: 09/03/2020
Last edited by couttsj; Sep 3rd, 2020 at 01:35 PM.
I converted Gossamer to utilize SimpleServer instead of the Winsock Control. It utilizes a single User Control, as mServer(0) serves as the listening socket and the remainder of the socket arrays service the connections. It provides the ability to use IPV6 instead of just IPV4, although this has not been tested as yet.
I tried to preserve as much of the original code as I could. It is not necessarily the way that I would normally do it. Each programmer has his/her own style. For example, the original code used STATIC_BUFFER_SZ As Long = 8192. SimpleServer uses whatever the operating system provides. In my case the buffer size is 65,536, and for the moment I have left the buffer size at 8,192 until I can figure out how to transfer that value from SimpleServer.
There is no reason that encrypted data cannot be integrated into this service. If I was to do it, I would incorporate TLS 1.3. This can be viewed as a starting point, as there is only a small part of TLS 1.2 that provides the same protection. All previous versions are obsolete/obfuscated.
This download does not include the contents of the \site or \art directories.
J.A. Coutts
Code:
Private Sub SendResponse(ByVal Status As Single, ByVal StatusText As String, ByVal MIME As String, ByVal ExtraHeaders As String)
AppendResp mStrHTTPVersion & " " & CStr(Status) & " " & StatusText & vbCrLf
AppendResp "Date:" & UTCString(Now()) & vbCrLf
If InStr(1, ExtraHeaders, "Last-Modified:", vbTextCompare) = 0 Then
AppendResp "Last-Modified:" & UTCString(Now()) & vbCrLf
End If
If Len(MIME) > 0 Then AppendResp "Content-Type:" & MIME & vbCrLf
mLngResponseLen = 0
On Error Resume Next
mLngResponseLen = UBound(mBytResponse) + 1
On Error GoTo 0
AppendResp "Content-Length:" & CStr(mLngResponseLen) & vbCrLf
AppendResp "Accept-Ranges:none" & vbCrLf
AppendResp ServerHeader & vbCrLf
If Len(ExtraHeaders) > 0 Then
AppendResp ExtraHeaders
If Right$(ExtraHeaders, 2) <> vbCrLf Then AppendResp vbCrLf
End If
AppendResp vbCrLf 'Second CRLF, terminating headers.
' mServer(mIntIndex).uOutBuffer = ExtractResp ā
mServer(mIntIndex).sOutBuffer = ExtractResp '
mServer(mIntIndex).TCPSend
End Sub
I figured out how to get the buffer size from SimpleServer to Gossamer, I had to add another Get to SimpleServer.
Code:
Friend Property Get BufferSize() As Long
BufferSize = m_lSendBufferLen
End Property
The buffer size is now obtained after the connection is established. But this introduced another problem. When I tried to send a file more than 32,767 bytes, the MOD function in ProcessRequest failed. I had to change mIntLastBlockSize to a Long value (mLngLastBlockSize).
xxdoc123;
Thanks for the tip. I obviously didn't test that part.
The download in Post #29 has been updated to reflect these changes.
On another topic, can anyone explain how the dynamic request is supposed to work?
So you have a lot of flexibility with the types of requests you can take and respond to.
you will want to put breakpoints in the function and then hit the server with various requests to see what is possible. A tool like Postman will be very valuable for discovery, testing, and development.
Last edited by DllHell; Sep 8th, 2020 at 12:08 PM.
I need some clarification on the use of "RespExtraHeaders". This is one of several variables (RespStatus, RespStatusText, RespMIME, RespExtraHeaders, and RespBody) that can get a return value in the DynamicRequest event because they are declared ByRef. DllHell has already demonstrated to us the use of RespStatus, RespStatusText, RespMIME, & RespBody, but the use of RespExtraHeaders evades me.
In the SendResponse Subroutine, it appears that if ExtraHeaders contains "Last-Modified:", it would get added twice. How and when are ExtraHeaders utilized?
I need some clarification on the use of "RespExtraHeaders". This is one of several variables (RespStatus, RespStatusText, RespMIME, RespExtraHeaders, and RespBody) that can get a return value in the DynamicRequest event because they are declared ByRef. DllHell has already demonstrated to us the use of RespStatus, RespStatusText, RespMIME, & RespBody, but the use of RespExtraHeaders evades me.
In the SendResponse Subroutine, it appears that if ExtraHeaders contains "Last-Modified:", it would get added twice. How and when are ExtraHeaders utilized?
it would get added twice. you want to use the extraheaders for things that are not already included: things specific to your server impl or your application.
important to note that the extraheaders are only useful if the receiving end knows how to handle them and are typically used for debugging or to provide additional info to developers about the request. For example, you may have a "X-powered-by: yourapp.exe/1.0.2" to know what version of your exe is on the other end.
Last edited by DllHell; Sep 25th, 2020 at 01:38 PM.
important to note that the extraheaders are only useful if the receiving end knows how to handle them and are typically used for debugging or to provide additional info to developers about the request. For example, you may have a "X-powered-by: yourapp.exe/1.0.2" to know what version of your exe is on the other end.
Once again, thank you for the info. You seem to be quite knowledgeable with reference to HTML code. What I hear you saying about this particular item, is that it is not really necessary to support it. I am trying to streamline the code in advance of introducing encryption. I am not trying to implement full web server support. One of the things I had to do was receive and send data as byte values instead of string. Encrypted data uses the full ANSI values rather than ACSII.