I'm not sure if it is container related, however it seems that the CorsFilter is not picked up automatically although the @WebFilter("/*") should do that:
@WebFilter("/*")
public class CorsFilter implements Filter
Instead I had to manually add to web.xml this:
<filter>
<filter-name>edu.harvard.iq.dataverse.filter.CorsFilter</filter-name>
<filter-class>edu.harvard.iq.dataverse.filter.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>edu.harvard.iq.dataverse.filter.CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
I also checked in the debugger and without the explicit web.xml doFilter() is not get called.
This topic was moved here from #containers > CorsFilter problem by Oliver Bertuch.
As this is not purely container related, I moved this here @Balázs Pataki
Are there any API tests for the filter?
It would be good to have a reproducer.
I just tried calling a DV endpoint from a browser component, but try to come up with a curl.
It would be good to have an API test included in our regular suite, so we can also test this is in CI
Actually, I don't know how to make curl fail with CORS, because it never does as it doesn't care, only browsers do, right? For now I just vibe coded a simple single page app which tries to create a dataset with POST and it does fail for me with stock develop branch DV (without manually adding the filter to webxml):
Just open it in a browser, set the api token and press "POST JSON" to see if it fails or not. (It uses the dataset-finch1.json from the DV API guides)
I was suggesting creating an IT test that checks for presence of the headers.
(As you said - anything that's not a browser won't care. But we'd only want to verify the servlet filter is picked up)
Ok, I created a CorsIT test checking for CORS headers.
It turns out that the result is kind of random based on whether the server happens to pick up CorsFilter or not.
Here's my actual test session: I run the app in docker. When it is started I run the test manually. Then I stop the server with ctrl+c and either start it again with run, or first do an explicit stop. None of these seem to matter, the result seems to be random:
mvn -Pct docker:stop
mvn -Pct docker:run --> FAIL
ctrl+c
mvn -Pct docker:run --> FAIL
ctrl+c
mvn -Pct docker:stop
mvn -Pct docker:run --> PASS
ctrl+c
mvn -Pct docker:run --> PASS
ctrl+c
mvn -Pct docker:run --> FAIL
I don't know how to proceed from here. One thing is sure: If add the explicit web.xml filter and filter-mapping it works consistently.
This is the test I run:
In the docker-compose-dev.yml I set these for dev_dataverse just to see if I get specific headers I configure:
DATAVERSE_CORS_ORIGIN: "*"
DATAVERSE_CORS_HEADERS_ALLOW: "Accept,Content-Type,X-Dataverse-key,Authorization,cedar-client-session-id,cedar-debug"
``
In your test I see that you are targeting an API endpoint.
Yes, /api/dataverses/root/datasets
Are we sure the filter actually will correctly work with JAX-RS
What happens when hitting JSF or other pure servlets?
Yes, sometimes it does, sometimes it doesn't. I don't know what kind of race condition could
it be.
What happens if we hit a 404 in a servlet
I don't know about JSF, but I want to access api endpoints from a webapp
What url do you suggest to test?
Technically, JAX-RS is still a servlet, but it's special. We may be in trouble here due to filter ordering etc.
Also, AI tells me there may be a problem with the mixed style of having web.xml registered filters and annotation based. Not sure if this really is a thing, but lets keep it in mind.
Yes, I think it definitely affects it, although as I read about it should be still deterministic
SwordFilter is the only one configured in web.xml. Maybe removing it from there and adding @WebFilter annotation on the class makes it clear.
I try this.
Technically for JAX-RS we should be using @ContainerRequestFilter to be within the consistent filter framework of JAX-RS
See ApiBlockingFilter in api.filter package.
So this may be another culprit, why things my go sideways - the servlet filter and the JAX-RS filter may conflict.
At least if both are discovered by annotations scanning.
Maybe making the filter explicit in web.xml makes it load before the other or sth
Hmm coming to think about it: any servlet filters will be applied first, then the JAX-RS internal filters will apply. These two don't mix-n-match, they are at different layers. So should be nothing to worry about here.
@Balázs Pataki could you please verify something for me...
Yes, sure.
Could you run the tests against /api/v1/...
I had a look at all the filters we already have and ApiRouter popped up. Asking AI about it gave me this:
The
ApiRouteris doing an internalRequestDispatcher.forward(...)from:
/api/...→/api/v1/...(your JAX‑RS app is mounted at )@ApplicationPath("api/v1")That has a big side effect in Servlet-land: a forward is a second dispatch with dispatcher type FORWARD, and many servlet filters only apply to dispatcher type REQUEST unless explicitly configured.
So let's rule out that the router may mess up our filter chain
In the meantime I tested putting @WebFilter onto SwordFilter and removing the web.xml config --> same random test behaviour
I now try the /api/v1 behaviour
Great to verify that! We doubted it would be related, glad to see it was indeed not.
My money is on Router vs Cors, as both affect the same endpoints.
Also, you might want to do the testing with the sword filter as well :wink: Not sure if the order matters with those two. IMHO filters should avoid being dependent on order. But of course we'd need to take care of types (forward, request)
Yep. So, when /api/dataverses/root/datasets fails /api/v1/dataverses/root/datasets passes the test.
Now what? :thinking:
In case of /api/v1 it first reaches CorsFilter then ApiBlockingFilter
In case of /api only reaches ApiBlockingFilter
(doFilter(), I mean)
Ha! So Api Router is the culprit
But you're somewhat wrong. The chain is:
/api -> Router -> forward -> Cors -> ApiBlocking (which breaks at Cors because forward)
/api/v1 -> Router -> Cors -> ApiBlocking
Maybe that's how it should be, but no, in case of /api Cors is not ALWAYS reached.
So in case of
RequestDispatcher dsp = request.getRequestDispatcher(newRequestUri);
dsp.forward(req, sr1);
wherever it goes it won't always goes thro CorsFilter, it is not in that filter chain, or sg.
So what we need to do: add both "FORWARD" and "REQUEST" to CorsFilter:
@WebFilter(value = "/*", dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.FORWARD})
We should also look at the types ERROR and INCLUDE later on to avoid breaking CORS when serving error pages etc
Balázs Pataki said:
Maybe that's how it should be, but no, in case of /api Cors is not ALWAYS reached.
It goes there, but will not match because of dispatcher type forward!
Ok, I check now adding DispatcherType.FORWARD
Please make sure to use the above example, otherwise we'd break REQUEST :wink:
yeah, i copy pasted that :-)
Oh yeah, it works consistently now! :tada:
And the filter orders are as follows:
/api
cors -> router -> cors -> apiblocking
/api/v1
cors -> router -> apiblocking
Good morning. :coffee: You've been busy!
Submitted a PR:
https://github.com/IQSS/dataverse/pull/12151
Should we add anything else to it, @Oliver Bertuch ?
Can you test the error pages?
And SWORD maybe?
You mean to add more URL-s to test in CorsIT, right?
Yeah, lets have more tests in there
Might be an @Parameterized one or single ones.
Not sure we can make RestAssured hit the other endpoints that easily.
@Don Sizemore this may be worth another backport to 6.9
I made it ParameterizedTest and now it tests these endpoints
"/api/dataverses/root/datasets",
"/api/v1/dataverses/root/datasets",
"/page_doesnt_exist",
"/dvn/api/data-deposit/v1.1/swordv2/collection/dataverse/root"
You probably should add CorsIT to the list of tests at https://github.com/IQSS/dataverse/blob/develop/tests/integration-tests.txt
I've always meant to refactor the API tests to properly use Maven Failsafe and JUnit 5 Suites, but didn't find the time so far...
I left two comments on the PR
Edit: added another one.
@Balázs Pataki I've marked your PR as a draft for now and added all the necessary labels (put didn't put it on the board yet @Philip Durbin 🚀). Keep them changes coming, I'll be the ambassador on including it in 6.10
I'd say we can safely put it in the "Ready for Triage" column so it comes up on Triage Tuesday. Any objections? Thanks for the PR! :heart:
@Balázs Pataki @Oliver Bertuch I'm reviewing #12151 and I rebased the branch. I hope you don't mind.
@Oliver Bertuch I added a note about requirements.txt changes that doc writers will need to know about.
@Don Sizemore tests are failing on Jenkins. Please see https://github.com/IQSS/dataverse/pull/12151#discussion_r3030000487
Should we enable CORS on the Dataverse instances Jenkins spins up? Can I co-assign you to the PR until we decide how to handle this?
@Balázs Pataki @Oliver Bertuch I have questions about #12151 and left reviews for you.
Last updated: Apr 03 2026 at 06:08 UTC