B4J Library [B4X] xHttpServer (Http Server + jQuery)

It is a personal project of mine that I started as a hobby on B4i and it has become an interesting project, so that I have modified it to be multiplatform.

It is an http server, which allows a browser to navigate on html pages stored on the device. In addition, dynamic pages can also be created. Read the parameters of the GET and POST commands and read and write the COOKIES in the browser.
It also implements the WebSocket, starting from an example published in this forum by @Erel for the jServer library. I also made the jQueryElement class to interface with the JavaScript elements of the Browser page.
(You can get the source of the QueryElement class at this Post)

Obviously it is not at the level of existing and established servers, but it is a project that I want to share and that I think can be useful since it is cross-platform. The examples are in post 2

It is entirely written in B4X.
It may have some bugs
It does not support SSL / TLS. I didn't understand how it works.


aHttpServer

Author:
Star-Dust
Version: 0.79
  • QueryElement
    • Events:
      • change (Resp As ServletResponse, Params As Map) ' QueryElement Event Click
      • click (Resp As ServletResponse, Params As Map) ' QueryElement Event Click
      • dblclick (Resp As ServletResponse, Params As Map) ' QueryElement Event Click
      • focus (Resp As ServletResponse, Params As Map) ' QueryElement Event Click
      • focusin (Resp As ServletResponse, Params As Map) ' QueryElement Event Click
      • focusout (Resp As ServletResponse, Params As Map) ' QueryElement Event Click
      • keyup (Resp As ServletResponse, Params As Map) ' QueryElement Event Click
      • mousedown (Resp As ServletResponse, Params As Map) ' QueryElement Event Click
      • mouseenter (Resp As ServletResponse, Params As Map) ' QueryElement Event Click
      • mouseleave (Resp As ServletResponse, Params As Map) ' QueryElement Event Click
      • mousemove (Resp As ServletResponse, Params As Map) ' QueryElement Event Click
      • mouseup (Resp As ServletResponse, Params As Map) ' QueryElement Event Click
    • Fields:
      • Event_change As String
      • Event_click As String
      • Event_dblclick As String
      • Event_focus As String
      • Event_focusin As String
      • Event_focusout As String
      • Event_keyup As String
      • Event_mousedown As String
      • Event_mouseenter As String
      • Event_mouseleave As String
      • Event_mousemove As String
      • Event_mouseup As String
      • NoEvent As Map()
    • Functions:
      • Class_Globals As String
      • CreateEvent (ObjectName As String, Event As String, OtherEvent As Map()) As Map()
      • EscapeHtml (Raw As String) As String
      • Eval (Script As String, Params As List) As String
      • EvalWithResult (Script As String, Params As List) As String
      • GetPropriety (Property As String, Value As List) As String
      • GetVal (ID As String, ValueList As List) As String
      • Initialize (Response As ServletResponse) As String
        Initializes the object. You can add parameters to this method if needed.
      • IsInitialized As Boolean
        Verifica se l'oggetto sia stato inizializzato.
      • RunFunction (function As String, ID As String, Params As List) As String
        Param = list or array: array as Map or String (array as Object is wrong)
      • RunFunctionWithResult (function As String, ID As String, Params As List) As String
      • RunMethod (Method As String, ID As String, Params As List) As String
        Param = list or array: array as Object is wrong - array as Map is correct
      • RunMethodWithResult (Method As String, ID As String, Params As List) As String
        Param = list or array: array as Object is wrong - array as Map is correct
      • SelectElement (ID As String) As String
      • SetCommand (etype As String, Method As String, property As String, ID As String, Params As List, Arg As List) As String
      • SetCSS (id As String, Params As List) As String
      • SetDialog (id As String, Params As List) As String
        Public Sub GetWidth As Object
        End Sub
      • SetHtml (id As String, Params As List) As String

        Public Sub SetHeight (Value As String)
        End Sub
      • SetPropriety (Property As String, Value As List) As String
      • SetText (ID As String, TextList As List) As String
      • SetVal (ID As String, ValueList As List) As String
    • Properties:
      • AutomaticEvents
  • ServletRequest
    • Fields:
      • CharacterEncoding As String
      • ConnectionAlive As Boolean
      • ContentLength As Long
      • ContentType As String
      • ID As String
      • LogActive As Boolean
      • LogFirstRefuse As Boolean
      • MultipartFilename As Map
      • RequestCookies As Map
      • RequestHeader As Map
      • RequestParameter As Map
      • RequestPostDataRow As List
      • Timeout As Long
    • Functions:
      • ArrayInsert (DataSource As Byte(), Index As Int, DataInsert As Byte()) As Byte()
      • ArrayRemove (Data As Byte(), Start As Int, Last As Int) As Byte()
      • Class_Globals As String
      • Close As String
      • Connected As Boolean
      • GetHeader (Name As String) As String
      • GetHeadersName As List
        can be used to iterate over Header
        Example
        <code>
        For Each Name As String In ServletRequest.GetHeadersName
        Log("Value = " & ServletRequest.GetHeader(Name))
        Next</code>
      • GetInputStream As InputStream
      • GetMethod As String
      • GetRequestHOST As String
      • GetRequestURI As String
      • GetWebSocketCompressDeflateAccept As Boolean
      • GetWebSocketCompressGzipAccept As Boolean
      • GetWebSocketMapData As Map
      • GetWebSocketStringData As String
      • Initialize (CallBack As Object, EventName As String, Sck As Socket) As String
        Initializes the object. You can add parameters to this method if needed.
      • IsInitialized As Boolean
        Verifica se l'oggetto sia stato inizializzato.
      • ParameterMap As Map
      • RemoteAddress As String
      • RemotePort As Int
      • SubArray2 (Data As Byte(), Start As Int, Last As Int) As Byte()
  • ServletResponse
    • Fields:
      • CharacterEncoding As String
      • ContentLenght As Int
      • ContentType As String
      • Status As Int
    • Functions:
      • Class_Globals As String
      • Close As String
      • Connected As Boolean
      • Initialize (Req As ServletRequest, ast As AsyncStreams, Sck As Socket) As String
        Initializes the object. You can add parameters to this method if needed.
      • IsInitialized As Boolean
        Verifica se l'oggetto sia stato inizializzato.
      • ResetCookies As String
      • SendFile (Dir As String, fileName As String) As String
        don't use DirAssets
      • SendFile2 (Dir As String, fileName As String, Content_Type As String) As String
      • SendNotFound (filenameNotFound As String) As String
      • SendRaw (Data As Byte()) As String
      • SendRedirect (Address As String) As String
      • SendString (Text As String) As String
        sending text with Header
      • SendWebSocketBinary (Data As Byte(), Masked As Boolean) As String
      • SendWebSocketClose As String
      • SendWebSocketPing As String
      • SendWebSocketPong As String
      • SendWebSocketString (Text As String, Masked As Boolean, Compressed As String) As String
        Cmpressed as Deflate=zlib, gzip, none - (set always none)
      • SetCookies (Name As String, Value As String) As String
        Set Cokies values on Browser
      • SetHeader (Name As String, Value As String) As String
      • Write (Text As String) As String
        Sending text without header to dynamically send more text after the SendString
    • Properties:
      • OutputStream As OutputStream [read only]
      • Query As QueryElement [read only]
  • httpServer
    • Events:
      • Handle (req As ServletRequest, resp As ServletResponse)
      • HandleWebSocket (req As ServletRequest, resp As ServletResponse)
      • NewConection (req As ServletRequest)
      • SwitchToWebSocket (req As ServletRequest, resp As ServletResponse)
      • UploadedFile (req As ServletRequest, resp As ServletResponse)
      • WebSocketClose (CloseCode As Int, CloseMessage As String)
    • Fields:
      • CharacterEncoding As String
      • DigestAuthentication As Boolean
      • DigestPath As String
      • htdigest As List
      • IgnoreNC As Boolean
      • realm As String
      • Timeout As Int
    • Functions:
      • Class_Globals As String
      • GetMyIP As String
      • GetMyWifiIp As String
      • Initialize (CallBack As Object, EventName As String) As String
        Initializes the object. You can add parameters to this method if needed.
      • IsInitialized As Boolean
        Verifica se l'oggetto sia stato inizializzato.
      • Start (Port As Int)
        eg. Start(51051)
      • Stop As String
    • Properties:
      • TempPath As String [read only]
 

Attachments

  • jHttpServer 0.79.zip
    35.9 KB · Views: 111
  • aHttpServer 0.79.zip
    35.4 KB · Views: 115
  • iHttpServer 0.79.zip
    457 KB · Views: 106
Last edited:

GiovanniO

Member
Hi @Star-Dust , I'm also experiencing the problem mentioned above. When I try to upload a file from my PC to my browser, I get an error:

Error:
*** Service (starter) Create ***
** Service (starter) Start **
** Activity (main) Create (first time) **
Call B4XPages.GetManager.LogEvents = True to enable logging B4XPages events.
** Activity (main) Resume **
New connection: 192.168.1.74 Counter: 1
New connection: 192.168.1.74 Counter: 2
Request URI: /fileupload.html
Request URI: /
Request URI: /styles.css
Request URI: /icon.png
Request URI: /favicon.ico
Request URI: /fileupload.html
Request URI: /stile.css
Request URI: /favicon.ico
New connection: 192.168.1.74 Counter: 3
index -1
java.lang.ArrayIndexOutOfBoundsException: length=0; index=0
    at b4a.example.servletrequest._vvvv6(servletrequest.java:129)
    at b4a.example.servletrequest._vvvvvv0(servletrequest.java:1232)
    at b4a.example.servletrequest._vvvvvv3(servletrequest.java:754)
    at b4a.example.servletrequest._vvvvvv7(servletrequest.java:1150)
    at b4a.example.servletrequest._astream_newdata(servletrequest.java:266)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:221)
    at anywheresoftware.b4a.BA$2.run(BA.java:395)
    at android.os.Handler.handleCallback(Handler.java:959)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loopOnce(Looper.java:249)
    at android.os.Looper.loop(Looper.java:337)
    at android.app.ActivityThread.main(ActivityThread.java:9515)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:636)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1005)
index -1
java.lang.ArrayIndexOutOfBoundsException: length=0; index=0
    at b4a.example.servletrequest._vvvv6(servletrequest.java:129)
    at b4a.example.servletrequest._vvvvvv0(servletrequest.java:1232)
    at b4a.example.servletrequest._vvvvvv3(servletrequest.java:754)
    at b4a.example.servletrequest._vvvvvv7(servletrequest.java:1150)
    at b4a.example.servletrequest._astream_newdata(servletrequest.java:266)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:221)
    at anywheresoftware.b4a.BA$2.run(BA.java:395)
    at android.os.Handler.handleCallback(Handler.java:959)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loopOnce(Looper.java:249)
    at android.os.Looper.loop(Looper.java:337)
    at android.app.ActivityThread.main(ActivityThread.java:9515)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:636)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1005)

This is what's happening to me with the example, i don't change nothing, and the file I'm trying to upload to the Android is 250kb.

Do you have any idea what might be wrong?

P.S.: Congratulations, it's a great project. I really like how you grouped several web "pages" with different functionalities into a server running on Android server.
 

Blueforcer

Well-Known Member
Licensed User
Longtime User
Since there is no fix yet, i started to diggin deeper into this problem

It appears that the internal URL decoding logic might not be checking the string length boundaries correctly before accessing characters following a % symbol.
If a string ends with a %, or if a % is followed by fewer than two characters (e.g., a malformed sequence or a split
occurring right after %), the parser seems to attempt to read the next two characters (for the hex code) without
verifying if they exist. This causes a crash with StringIndexOutOfBoundsException: length=X; index=X.
Try to add a boundary check before accessing the subsequent characters in your URL decoding method.

i would be happy if you can make the lib opensource, so i can help you to fix the problem if you have not time for that
 
Last edited:

Star-Dust

Expert
Licensed User
Longtime User
Hi @Star-Dust , I'm also experiencing the problem mentioned above. When I try to upload a file from my PC to my browser, I get an error:

Error:
*** Service (starter) Create ***
** Service (starter) Start **
** Activity (main) Create (first time) **
Call B4XPages.GetManager.LogEvents = True to enable logging B4XPages events.
** Activity (main) Resume **
New connection: 192.168.1.74 Counter: 1
New connection: 192.168.1.74 Counter: 2
Request URI: /fileupload.html
Request URI: /
Request URI: /styles.css
Request URI: /icon.png
Request URI: /favicon.ico
Request URI: /fileupload.html
Request URI: /stile.css
Request URI: /favicon.ico
New connection: 192.168.1.74 Counter: 3
index -1
java.lang.ArrayIndexOutOfBoundsException: length=0; index=0
    at b4a.example.servletrequest._vvvv6(servletrequest.java:129)
    at b4a.example.servletrequest._vvvvvv0(servletrequest.java:1232)
    at b4a.example.servletrequest._vvvvvv3(servletrequest.java:754)
    at b4a.example.servletrequest._vvvvvv7(servletrequest.java:1150)
    at b4a.example.servletrequest._astream_newdata(servletrequest.java:266)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:221)
    at anywheresoftware.b4a.BA$2.run(BA.java:395)
    at android.os.Handler.handleCallback(Handler.java:959)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loopOnce(Looper.java:249)
    at android.os.Looper.loop(Looper.java:337)
    at android.app.ActivityThread.main(ActivityThread.java:9515)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:636)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1005)
index -1
java.lang.ArrayIndexOutOfBoundsException: length=0; index=0
    at b4a.example.servletrequest._vvvv6(servletrequest.java:129)
    at b4a.example.servletrequest._vvvvvv0(servletrequest.java:1232)
    at b4a.example.servletrequest._vvvvvv3(servletrequest.java:754)
    at b4a.example.servletrequest._vvvvvv7(servletrequest.java:1150)
    at b4a.example.servletrequest._astream_newdata(servletrequest.java:266)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:221)
    at anywheresoftware.b4a.BA$2.run(BA.java:395)
    at android.os.Handler.handleCallback(Handler.java:959)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loopOnce(Looper.java:249)
    at android.os.Looper.loop(Looper.java:337)
    at android.app.ActivityThread.main(ActivityThread.java:9515)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:636)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1005)

This is what's happening to me with the example, i don't change nothing, and the file I'm trying to upload to the Android is 250kb.

Do you have any idea what might be wrong?

P.S.: Congratulations, it's a great project. I really like how you grouped several web "pages" with different functionalities into a server running on Android server.
Hello,

As you are not a licensed user, I am not always notified of your messages because some time passes before approval and in the meantime it is covered by other more recent messages.

For your problem I should have an example to try
 
Last edited:

Star-Dust

Expert
Licensed User
Longtime User
i would be happy if you can make the lib opensource, so i can help you to fix the problem if you have not time for that
The library is not open source. These libraries are created for my work and required many months of work because they were built from scratch and without using third-party libraries. I make them available for free even though they required a lot of work. The request to also have the source seems excessive to me.
 

Blueforcer

Well-Known Member
Licensed User
Longtime User
The library is not open source. These libraries are created for my work and required many months of work because they were built from scratch and without using third-party libraries. I make them available for free even though they required a lot of work. The request to also have the source seems excessive to me.
Don't worry, it was just a (valid) question with a legitimate answer. I just wanted to help; the code is of no use to me otherwise.
I have now written my own HTTP server that has no problems with uploads. For my purposes, it was quicker to implement than writing a patch for the Java classes.

If you still want to take a look at the problem, I gave you a tip above on where you could look. There also seems to be another problem:
The crash may also depends on how the browser or network segments the data packets (fragmentation), so it could be hard to reproduce it:
If the HTTP request is received in a way where the header containing the boundary definition (------WebKitFormBoundary...) is split across two packets or hasn't arrived in the current AsyncStream chunk, the code fails to find the boundary string. The logic needs a safety check to ensure the Boundary string is not empty (length > 0) before attempting to use it. If the boundary hasn't been found in the current packet, the method should probably wait for more data instead of crashing.

You may can reproduce it with the attached python test script. There you have the option to force the crash and to force a valid upload (wich normaly depends on the browser)
 

Attachments

  • bugTester.py
    3.9 KB · Views: 1
Last edited:

Star-Dust

Expert
Licensed User
Longtime User
You did well to build your own http server, it was exactly the thing I wanted to suggest to you, you can have the freedom to make changes to your code.

Regarding your suggestion, I thank you, but not knowing how I developed my code it is risky because you are not aware that before processing the strings wait for all the packets to arrive and for the total number of bytes to correspond to that declared in the header. So it doesn't process packets as they arrive as you assumed.

I'm sorry but I'm always annoyed with those who think they can correct or improve code they don't know. However, I really appreciate that you created your own server, it's an important exercise that allows you to understand the inside of things, I give you credit for this.

Finally, if you wish, make your library available for free to others and make your code available if you think it is right
 

Blueforcer

Well-Known Member
Licensed User
Longtime User
I don't think you understand that I only have good intentions and want to help you and the users. I don't think that's a reason to oppose it.
Several users have the same error that you couldn't reproduce (which is totally legit).
I was able to reproduce the error through many many tests and logically concluded the problem. I'm giving you the chance to reproduce the problem yourself with the script.
Whether you accept the help or stay upset is up to you. Greetings
 
Last edited:
Top