Skip to content

Commit

Permalink
Allow frontend dev server to access Tomcat backend
Browse files Browse the repository at this point in the history
Enables simultaneous development of the frontend code served by Vite and
the backend code served by Tomcat.

In dev mode:

  STRCALC_BACKEND='http://localhost:8080/strcalc/' pnpm dev

In preview mode (pnpm build && pnpm preview), the STRCALC_BACKEND value
will not propagate to the compiled bundle. However, entering the
following in the browser console will enable the compiled version to
communciate with the backend:

  globalThis.STRCALC_BACKEND='http://localhost:8080/strcalc/'

This required the following steps:

- Setting the STRCALC_BACKEND property of `define` in vite.config.js
  based on the STRCALC_BACKEND environment variable. This sets
  `globalThis.STRCALC_BACKEND` in the browser environment.

- Adding calculators.backendUrl() to determine the URL that
  backendCalculator() (formerly defaultPost() uses.

- Setting the Tomcat CORS filter in the app's web.xml file per:

  - https://stackoverflow.com/a/18850438
  - https://tomcat.apache.org/tomcat-10.1-doc/config/filter.html#CORS_Filter
  - https://tomcat.apache.org/tomcat-10.1-doc/images/cors-flowchart.png
  - https://tomcat.apache.org/tomcat-10.1-doc/api/org/apache/catalina/filters/CorsFilter.html

Without the CORS configuration, the fetch() from the browser to the
backend will fail, and the browser console will show something like:

  [Error] Origin http://localhost:5173 is not allowed by
          Access-Control-Allow-Origin. Status code: 200
  [Error] Fetch API cannot load http://localhost:8080/strcalc/add due to
          access control checks.
  [Error] Failed to load resource: Origin http://localhost:5173 is not
          allowed by Access-Control-Allow-Origin. Status code: 200 (add,
          line 0)
  • Loading branch information
mbland committed Dec 19, 2023
1 parent 5a22ec9 commit 75858de
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 8 deletions.
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,47 @@ plugin:
- Configure the selected plugin to process the downloaded
`jacocoXmlTestReport.xml` file.

## Allow frontend dev server to access Tomcat backend

It's possible to develop the frontend code served by Vite and
the backend code served by Tomcat simultaneously.

### Start Tomcat

For now, this requires running a fresh build of the app in Docker or setting
up your own IntelliJ IDEA Run Configuration. I hope to have another solution
set up shortly that would require neither.

```sh
# Compile the current code into strcalc.war.
./gradlew build

# Serve the strcalc.war file via Docker
./bin/tomcat-docker.sh
```

This is enabled by the `CORSFilter` settings in the application's
[web.xml](./strcalc/src/main/webapp/WEB-INF/web.xml) file. See the comment
in that file for further references.

### Start frontend dev server

```sh
cd strcalc/src/main/frontend
STRCALC_BACKEND='http://localhost:8080/strcalc/' pnpm dev
```

### Start frontend preview server

In preview mode (`pnpm build && pnpm preview`), the STRCALC_BACKEND value
will not propagate to the compiled bundle. However, entering the
following in the browser console will enable the compiled version to
communciate with the backend:

```js
globalThis.STRCALC_BACKEND='http://localhost:8080/strcalc/'
```

## Adding large tests

Coming soon...
Expand Down
11 changes: 9 additions & 2 deletions strcalc/src/main/frontend/components/calculators.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,23 @@ import { postFormData } from './request'

export const DEFAULT_ENDPOINT = './add'

const defaultPost = async (data)=> postFormData(DEFAULT_ENDPOINT, data)
const backendUrl = () => globalThis.STRCALC_BACKEND ?
new URL(DEFAULT_ENDPOINT, globalThis.STRCALC_BACKEND).toString() :
DEFAULT_ENDPOINT

const backendCalculator = async (data)=> postFormData(backendUrl(), data)

const tempCalculator = async (data) => Promise.reject(new Error(
`Temporary in-browser calculator received: "${data.get('numbers')}"`
))

/**
* Collection of production String Calculator implementations
*
* Each implementation takes a FormData instance containing only a
* 'numbers' field as its single argument.
*/
export default {
'api': { label: 'Tomcat backend API (Java)', impl: defaultPost },
'api': { label: 'Tomcat backend API (Java)', impl: backendCalculator },
'browser': { label: 'In-browser (JavaScript)', impl: tempCalculator }
}
26 changes: 20 additions & 6 deletions strcalc/src/main/frontend/components/calculators.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,27 @@ describe('calculators', () => {

afterEach(() => { vi.unstubAllGlobals() })

test('defaultPost requests expected backend', async () => {
const data = setupData('2,2')
const fetchStub = setupFetchStub(JSON.stringify({ result: 5 }))
describe('defaultPost', () => {
test('posts same server by default', async () => {
const data = setupData('2,2')
const fetchStub = setupFetchStub(JSON.stringify({ result: 5 }))

await expect(calculators.api.impl(data)).resolves.toEqual({ result: 5 })
expect(fetchStub).toHaveBeenCalledWith(
DEFAULT_ENDPOINT, postOptions({ numbers: '2,2' }))
})

test('posts to globalThis.STRCALC_BACKEND', async () => {
const data = setupData('2,2')
const fetchStub = setupFetchStub(JSON.stringify({ result: 5 }))
vi.stubGlobal('STRCALC_BACKEND', 'http://localhost:8080/strcalc/')

await expect(calculators.api.impl(data)).resolves.toEqual({ result: 5 })
expect(fetchStub).toHaveBeenCalledWith(
DEFAULT_ENDPOINT, postOptions({ numbers: '2,2' }))
await expect(calculators.api.impl(data)).resolves.toEqual({ result: 5 })
expect(fetchStub).toHaveBeenCalledWith(
new URL(DEFAULT_ENDPOINT, 'http://localhost:8080/strcalc/').toString(),
postOptions({ numbers: '2,2' })
)
})
})

test('tempCalculator rejects with Error', async () => {
Expand Down
3 changes: 3 additions & 0 deletions strcalc/src/main/frontend/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export default defineConfig({
plugins: [
handlebarsPrecompiler({ helpers: ['components/helpers.js'] })
],
define: {
STRCALC_BACKEND: JSON.stringify(process.env.STRCALC_BACKEND)
},
build: {
outDir: buildDir('webapp'),
sourcemap: true
Expand Down
19 changes: 19 additions & 0 deletions strcalc/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,23 @@
jakarta.enterprise.inject.spi.BeanManager
</resource-env-ref-type>
</resource-env-ref>

<!-- Set CORS headers to run frontend and backend separately.
- https://stackoverflow.com/a/18850438
- https://tomcat.apache.org/tomcat-10.1-doc/config/filter.html#CORS_Filter
- https://tomcat.apache.org/tomcat-10.1-doc/images/cors-flowchart.png
- https://tomcat.apache.org/tomcat-10.1-doc/api/org/apache/catalina/filters/CorsFilter.html
-->
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.origins</param-name>
<param-value>http://localhost:5173, http://localhost:4173</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

0 comments on commit 75858de

Please sign in to comment.