B4J Question Creating B4J server and static web pages site - how to

Mark Stuart

Well-Known Member
Licensed User
Longtime User
Hi y'all,

I'm wanting to setup a web site and a web server. The web server will be a B4J app. It has nothing to do with the static web pages.
So 2 different entities.

I've had help with the setup of a Ubuntu on DigitalOcean.
Installed so far:
1. MS SQL server
2. JRE version 21.0.9
3. Docklet that supports the above?

The B4J app and files have not been installed as of yet, as I'm still developing and testing that.
What I need to know is has anyone done both on the same host so that the website and the B4J web server is running on the same VPS. Possibly 2 Docklets?

Some say to install NGINX and it will receive the incoming client data (JSON) and pass it to the appropriate B4J server port. eg: 8080
The website is listening to ports 80 and 443, right?

Internet
|
v
Nginx (80 / 443) --> status website
Java B4J (8080) --> backend only

Is this correct?

But how to do this has me puzzled as I do not know server stuff or Linux, etc. Just how to develop apps. (old cliche, right?)

I need help on this please.

Regards,
Mark Stuart
 

aeric

Expert
Licensed User
Longtime User
You can just run B4J server to serve static website without nginx. Unless you want to run PHP on port 80/443.
 
Upvote 0

Mark Stuart

Well-Known Member
Licensed User
Longtime User
You can just run B4J server to serve static website without nginx. Unless you want to run PHP on port 80/443.
I'll be running the 2 entities separate. They won't be talking to one another, at this point.
Maybe down the road sometime.

The issue is what are the settings for ports:
1. Static website
2. B4j server
How is all that setup so they don't conflict?
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
The issue is what are the settings for ports:
1. Static website
2. B4j server
How is all that setup so they don't conflict?
So as you mentioned, set your "static" website to use port 80/443 using nginx (it should be set by default when you install nginx)
For B4J server, use other port number as long as it is not conflicting with other servers or services. Port 8080 is good to go. (You can set the port number in srvr.Port)
 
Upvote 0

Mark Stuart

Well-Known Member
Licensed User
Longtime User
You can just run B4J server to serve static website without nginx. Unless you want to run PHP on port 80/443.
you said "without nginx..."
Could you please elaborate on that, aeric?

I'm trying that but haven't been able to get it working successfully on the linux server.
No problem during development on my PC.

CSS and images are in a subfolder to the .jar file:

/home/b4jtest/

├── markwebserver.jar ← B4J server app (running)

├── index.html ← Home page
├── labelforge.html ← Product detail page
├── labelscan.html ← Product detail page
├── privacy.html ← Privacy Policy
├── eula.html ← EULA
├── template.html ← Template (if used)

├── assets/ ← Static assets (served manually)
│ │
│ ├── css/
│ │ └── base.css ← Main stylesheet
│ │
│ └── images/
│ └── logo.png ← Logo and other images

├── logs/
│ └── server.log ← Application logs

└── markwebserver.out ← Optional output file

What happens:
- index.html loads
- css and images do not load
- Links to the other html pages fails as well.
- all generating a 404 not found in the browser Inspect view and in the browser.

Need to understand why the app and browser does not load the static files of: css and images.
Yes I know this is completely outside the www structure but I need to know how to get this to work, if possible.

AppStart:
Sub Process_Globals
    Public srvr As Server
    Public sql As SQL
End Sub

Sub AppStart (Args() As String)
    Log("Initializing SQL..")
    
    srvr.Initialize("")
    srvr.Port = 80
    
    'Enable HTTPS on 443 using your keystore:
    ConfigureSSL(443)
    
    'srvr.StaticFilesFolder = File.Combine(File.DirApp,"/assets")
    'Log("StaticFilesFolder: " & File.Combine(File.DirApp,"www"))
    'Optional: redirect http -> https
    srvr.AddFilter("/*", "HttpsFilter", False)
    
    'Your virtual-host style router:
    srvr.AddHandler("/*", "HostRouter", False)
    
    srvr.Start
    
    Log($"Listening on http:${srvr.Port} and https:${srvr.SslPort}"$)
    
    StartMessageLoop
End Sub

Sub sql_Ready (Success As Boolean)
    If Success = False Then
        Log("DB connect failed: " & LastException.Message)
        Return
    End If

    Log("DB connected!")

    Insert_ActivityLog
End Sub

Sub Insert_ActivityLog
    Dim q As String = _
            $"INSERT INTO [dbo].[ActivityLog]
               ([AndroidID],[Module],[LocCode],[Role],[Event],[Result],[Timestamp])
             OUTPUT INSERTED.[LogID]
             VALUES (?,?,?,?,?,?,?)"$

    Dim args As List = Array As Object( _
            "test-android-id-123", _
            "MarkTestServer", _
            "NZ-WLG", _
            "Admin", _
            "Hello World From Mark Test Server", _
            "OK", _
            DateTime.Now _
        )

    Dim rs As ResultSet = sql.ExecQuery2(q, args)
    If rs.NextRow Then
        Log("Inserted LogID: " & rs.GetInt("LogID"))
    End If
    rs.Close
End Sub


Private Sub ConfigureSSL(SslPort As Int)
    Dim ssl As SslConfiguration
    ssl.Initialize
    ssl.SetKeyStorePath("/ssl", "jetty.keystore")
    ssl.KeyStorePassword = "*********"
    ssl.KeyManagerPassword = "**********"
    srvr.SetSslConfiguration(ssl, SslPort)
End Sub

HostRouter - Handler code:
Public Sub Handle(req As ServletRequest, resp As ServletResponse)
    Dim host As String = req.GetHeader("host").ToLowerCase
    Log("HOST: " & host)

    'Host header can include ":443" - strip it.
    If host.Contains(":") Then host = host.SubString2(0, host.IndexOf(":"))
    
    'This will be for marktest.anywheremediacompany.com
    If (host = "proudbaystudio.com" Or host = "134.199.193.167") Then
        If(req.Method = "GET") Then 'ALL GET REQUESTS
            Log("GET METHOD")
            Log("DIRAPP " & File.DirApp)
            Select Case req.RequestURI
                Case "/" 'Home Page
                    resp.ContentType = "text/html; charset=utf-8"
                    resp.Status = 200
                    resp.Write(File.ReadString(File.DirApp, "index.html"))
                    Return
'                Case "/about-us"
'                    resp.ContentType = "text/html; charset=utf-8"
'                    resp.Status = 200
'                    resp.Write(File.ReadString(File.DirApp, "aboutus.html"))
'                    Return
            End Select
        
        Else If(req.Method = "POST") Then'ALL POST REQUESTS
            Log("POST METHOD")
            Select Case req.RequestURI
                'api endpoints
                Case "/api/device_check_in"
                    Dim in As InputStream = req.InputStream
                    Dim dataB() As Byte
                    dataB = Bit.InputStreamToBytes(in)
                    Dim body As String = BytesToString(dataB, 0, dataB.Length, "UTF8")
                    
                    ' Parse the JSON string
                    Dim check_in_data As JSONParser
                    check_in_data.Initialize(body)
                    Dim receivedMap As Map = check_in_data.NextObject
                    
                    Dim device_id As Int = receivedMap.get("device_id")
                    Dim device_type As String = receivedMap.get("device_type")
                    Dim customer_token_key As String = receivedMap.get("customer_token_key")
                    
                    
                    If(customer_token_key = "customer1_dfgsdfgsdfglsdkfhsfghjfdghdfgh") Then
                        
                    Else if (customer_token_key = "customer2_fgsdfghkdfghdfghdfghdfgh") Then
                            
                    End If
                    
                    'submit to database and return 200 OK http packet
                    Log("DEVICE INFO: " & device_id & " " & device_type)
                    
                    resp.Status = 200
                    resp.ContentType = "text/plain; charset=utf-8"
                    resp.Write("OK")
                    Return
            End Select
            
        End If
        
    End If

    resp.Status = 404
    resp.ContentType = "text/plain; charset=utf-8"
    resp.Write("Not Found - " & host)
End Sub
 
Upvote 0

John Naylor

Active Member
Licensed User
Longtime User
How I do it -

Nginx in a container handles all SSL traffic which makes things easier on the back end.

My conf file is set up such that if I access the server via www.domain1.com, nginx sends traffic to a ReactJS container on port 1111 but if I access via www.domain2.com it gets sent to a B4J server on port 2222. I have a PostGres database running on another port and a Django API application on yet another.

I use certbot for certificates tied to Nginx which is set to only accept SSL. All the other instances (PostGres, B4J, Django etc etc just run on normal http.)

I put each system in its own container then join them together with a docker network. As such there is only one way in or out of the system and that is through Nginx.

Example nginx.conf for two domains. This can be changed for multiple locations on the same domain or to open up different ports, whatever you need.


B4X:
# /etc/nginx/conf.d/reverse-proxy.conf
# Assumes you’re terminating TLS at nginx and proxying to:
#   - React app at http://react-app:8000
#   - B4J server at http://b4j-server:9999

# Optional (nice to have for websocket upgrade handling)
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

# ---------------------------
# DOMAIN 1 (React) - HTTPS
# ---------------------------
server {
  listen 443 ssl http2;
  server_name domain1.com www.domain1.com;

  ssl_certificate     /etc/letsencrypt/live/domain1.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem;

  # (optional but common)
  # include /etc/letsencrypt/options-ssl-nginx.conf;
  # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

  location / {
    proxy_pass http://react-app:8000;
    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # If your React dev server / SSR uses websockets, keep these:
    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
  }
}

# HTTP -> HTTPS for domain1
server {
  listen 80;
  server_name domain1.com www.domain1.com;

  location /.well-known/acme-challenge/ {
    root /var/www/certbot;
  }

  return 301 https://$host$request_uri;
}

# ---------------------------
# DOMAIN 2 (B4J) - HTTPS
# ---------------------------
server {
  listen 443 ssl http2;
  server_name domain2.com www.domain2.com;

  ssl_certificate     /etc/letsencrypt/live/domain2.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/domain2.com/privkey.pem;

  location / {
    proxy_pass http://b4j-server:9999;
    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # If your B4J app uses websockets / SSE, keep these:
    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
  }
}

# HTTP -> HTTPS for domain2
server {
  listen 80;
  server_name domain2.com www.domain2.com;

  location /.well-known/acme-challenge/ {
    root /var/www/certbot;
  }

  return 301 https://$host$request_uri;
}
 
Last edited:
Upvote 0

Mark Stuart

Well-Known Member
Licensed User
Longtime User
How I do it -

Nginx in a container handles all SSL traffic which makes things easier on the back end.

My conf file is set up such that if I access the server via www.domain1.com, nginx sends traffic to a ReactJS container on port 1111 but if I access via www.domain2.com it gets sent to a B4J server on port 2222. I have a PostGres database running on another port and a Django API application on yet another.

I use certbot for certificates tied to Nginx which is set to only accept SSL. All the other instances (PostGres, B4J, Django etc etc just run on normal http.)

I put each system in its own container then join them together with a docker network. As such there is only one way in or out of the system and that is through Nginx.

Example nginx.conf for two domains. This can be changed for multiple locations on the same domain or to open up different ports, whatever you need.


B4X:
# /etc/nginx/conf.d/reverse-proxy.conf
# Assumes you’re terminating TLS at nginx and proxying to:
#   - React app at http://react-app:8000
#   - B4J server at http://b4j-server:9999

# Optional (nice to have for websocket upgrade handling)
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

# ---------------------------
# DOMAIN 1 (React) - HTTPS
# ---------------------------
server {
  listen 443 ssl http2;
  server_name domain1.com www.domain1.com;

  ssl_certificate     /etc/letsencrypt/live/domain1.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/domain1.com/privkey.pem;

  # (optional but common)
  # include /etc/letsencrypt/options-ssl-nginx.conf;
  # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

  location / {
    proxy_pass http://react-app:8000;
    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # If your React dev server / SSR uses websockets, keep these:
    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
  }
}

# HTTP -> HTTPS for domain1
server {
  listen 80;
  server_name domain1.com www.domain1.com;

  location /.well-known/acme-challenge/ {
    root /var/www/certbot;
  }

  return 301 https://$host$request_uri;
}

# ---------------------------
# DOMAIN 2 (B4J) - HTTPS
# ---------------------------
server {
  listen 443 ssl http2;
  server_name domain2.com www.domain2.com;

  ssl_certificate     /etc/letsencrypt/live/domain2.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/domain2.com/privkey.pem;

  location / {
    proxy_pass http://b4j-server:9999;
    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # If your B4J app uses websockets / SSE, keep these:
    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
  }
}

# HTTP -> HTTPS for domain2
server {
  listen 80;
  server_name domain2.com www.domain2.com;

  location /.well-known/acme-challenge/ {
    root /var/www/certbot;
  }

  return 301 https://$host$request_uri;
}
John, thanx for the info, but I am not experienced in Linux server. So this is all foreign language to me.

I've been shown the above B4J method of handling both static website and B4J web server functions, but as I've mentioned, the static website is not functional.
That's my main concern and priority at this time, so that I can then get the Google Play store setup for my customer for installing my private apps.

If I have to go the NGINX route, then so be it.
If that's the case then I would assume then the normal "www" way of setting up the B4J web server.

Regards,
Mark Stuart
 
Upvote 0

John Naylor

Active Member
Licensed User
Longtime User
I'm not sure your server knows where your static files are. I'm not at my PC right now but by the looks of it you have commented out where your static files are held. You can point things in the right direction with (*untested - from memory)

B4X:
srvr.StaticFilesFolder = File.Combine(File.DirApp,"/")
 
Upvote 0

John Naylor

Active Member
Licensed User
Longtime User
That exposes your jar file though. Best to put all your folders into a www folder and keep your jar file at the top level.


/home/b4jtest/

├── markwebserver.jar ← B4J server app (running)
├── www
All your other files here

Then add www as your static files folder

B4X:
srvr.StaticFilesFolder = File.Combine(File.DirApp,"/www")
 
Upvote 0

Mark Stuart

Well-Known Member
Licensed User
Longtime User
It seems to ignore the StaticFilesFolder value ("/www") as with the error:
FileNotFoundException: /home/b4jtest/index.html
It's missing the www in the error text.

Same changing it to:
srvr.StaticFilesFolder = File.Combine(File.DirApp,"www")
 
Upvote 0

John Naylor

Active Member
Licensed User
Longtime User
It seems to ignore the StaticFilesFolder value ("/www") as with the error:
FileNotFoundException: /home/b4jtest/index.html
It's missing the www in the error text.
I might have syntax wrong above Mark I will have to run up a test app on a server when I get home. It'll be tomorrow before I can get to it though
 
Upvote 0

John Naylor

Active Member
Licensed User
Longtime User
The example here actually does what you are needing - Static assets (html and images) in the www folder keeping the jar file safe. You just need to modify your writefile line to

B4X:
resp.Write(File.ReadString(File.DirApp, "www/index.html"))
 
Upvote 0

Mark Stuart

Well-Known Member
Licensed User
Longtime User
I've read Erel's post on that and my current server folder structure is as he has outlined.
The main issues are that the html files can't "see" the sub folder assets to load the css and images.
I click on a link in the index.html and it goes to the page with the body stating "Not Found".
That I think I can fix with B4J. Having to add each html file to the Select Case statement.
That I'll try now.

Regards,
Mark
 
Upvote 0

John Naylor

Active Member
Licensed User
Longtime User
In your html, how are you linking to your style sheet?

B4X:
<link rel="stylesheet" type="text/css" href="assets/css/base.css">
 
Upvote 0

Mark Stuart

Well-Known Member
Licensed User
Longtime User
<link rel="stylesheet" href="assets/css/base.css">
<link rel="stylesheet" href="assets/css/header-home.css">
 
Upvote 0

Mark Stuart

Well-Known Member
Licensed User
Longtime User
srvr.StaticFilesFolder = File.Combine(File.DirApp,"www")
Try put "assets" folder inside "www" folder.
Hi aeric, yes that's where all the .html files are - in the www folder.
Also the assets folder is under the www folder as well.
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
Is your folder structure look like this?

/home/b4jtest/
/home/b4jtest/markwebserver.jar
/home/b4jtest/www/
/home/b4jtest/www/index.html
/home/b4jtest/www/assets/
/home/b4jtest/www/assets/css/
/home/b4jtest/www/assets/css/base.css
 
Last edited:
Upvote 0

Mark Stuart

Well-Known Member
Licensed User
Longtime User
Finally got this code to load all the website pages:

B4X:
Select Case req.RequestURI
                Case "/" 'Index Page
                    resp.ContentType = "text/html; charset=utf-8"
                    resp.Status = 200
                    resp.Write(File.ReadString(File.DirApp, "www/index.html"))
                    Return
                Case "/eula.html"
                    resp.ContentType = "text/html; charset=utf-8"
                    resp.Status = 200
                    resp.Write(File.ReadString(File.DirApp, "www/eula.html"))
                    Return
                Case "/privacy.html"
                    resp.ContentType = "text/html; charset=utf-8"
                    resp.Status = 200
                    resp.Write(File.ReadString(File.DirApp, "www/privacy.html"))
                    Return
                Case "/labelforge.html"
                    resp.ContentType = "text/html; charset=utf-8"
                    resp.Status = 200
                    resp.Write(File.ReadString(File.DirApp, "www/labelforge.html"))
                    Return
                Case "/labelscan.html"
                    resp.ContentType = "text/html; charset=utf-8"
                    resp.Status = 200
                    resp.Write(File.ReadString(File.DirApp, "www/labelscan.html"))
                    Return
            End Select
Now for css and images from the assets folder.

Any suggestions on that would be appreciated.
Thanx,
Mark Stuart
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
Finally got this code to load all the website pages:

B4X:
Select Case req.RequestURI
                Case "/" 'Index Page
                    resp.ContentType = "text/html; charset=utf-8"
                    resp.Status = 200
                    resp.Write(File.ReadString(File.DirApp, "www/index.html"))
                    Return
                Case "/eula.html"
                    resp.ContentType = "text/html; charset=utf-8"
                    resp.Status = 200
                    resp.Write(File.ReadString(File.DirApp, "www/eula.html"))
                    Return
                Case "/privacy.html"
                    resp.ContentType = "text/html; charset=utf-8"
                    resp.Status = 200
                    resp.Write(File.ReadString(File.DirApp, "www/privacy.html"))
                    Return
                Case "/labelforge.html"
                    resp.ContentType = "text/html; charset=utf-8"
                    resp.Status = 200
                    resp.Write(File.ReadString(File.DirApp, "www/labelforge.html"))
                    Return
                Case "/labelscan.html"
                    resp.ContentType = "text/html; charset=utf-8"
                    resp.Status = 200
                    resp.Write(File.ReadString(File.DirApp, "www/labelscan.html"))
                    Return
            End Select
Now for css and images from the assets folder.

Any suggestions on that would be appreciated.
Thanx,
Mark Stuart
The proper way is not to put html file inside www folder. It is fine if you are doing a test.
The recommended way is to put the html files inside "Files" folder during development and load them from File.DirAssets so it will be compiled inside the jar file.
 
Upvote 0
Top