running form javaws - jakarta.ws.rs.client is not sending requests after first one? Client caching?

Viewed 444

I have a Java client application that sends REST request to retrieve some data form a REST service.

I'm setting the client up like this:

import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Response;
import org.glassfish.jersey.client.filter.EncodingFilter;
import org.glassfish.jersey.message.GZipEncoder;


SSLContext sslContext = SSLClientUtil.getSSLContext(getCertificate(keystore), keystorePassword);
this.client = ClientBuilder.newBuilder()
        .sslContext(sslContext)
        .hostnameVerifier(new TrustedHostnameVerifier())
        .build();
client.register(GZipEncoder.class);
client.register(EncodingFilter.class);

this.webTarget = client.target("https://example.org:443/service/rest");

and in another method doing the actual request to the server

//Get Method
Response response = this.webTarget.request().get()
// Extract the content
response.close();
// return the content

When I run my application, the first time I run the Get-Method, the service returns the response and I get my response ... but after this first time. The request is not even sent to the server (I can see no network traffic on the eth interface of my client.

It seem like the client is somehow caching the response and is not sending the request again. I've seen that the server sends the response with the header Cache-Control: private. How can I convince my client to send the actual request once more (and if) do not use any cached response?

Note: I tried already in my client to set in the headers Cache-control directives ... but still the request is not sent with the method (this.webTarget.request().get()).

Update: After some testing it seems to have something to do that the java client application is running as an javaws application. If started from debugger (without javaws) the behavior is as expected. Running it as javaws the request is cached.
This seems to be similar to the entry here where a javaws application is retrieving documents using URLConnection that are cached. There the connection can be set to not caching.
How can this be done for jakarta.ws.rs / jersey ?

2 Answers

Overview

I tried to reproduce your problems and

  • created a simple Spring Boot demo REST service
    which returns always a current timestamp, so cached values etc. would be simple identified.
  • started the Spring Boot demo application
  • created a swing demo client which uses JAX-RS and Jersey
  • wrote a simple JNLP file
  • built a fat jar by gradle
  • (self-)signed the fat jar
  • copied jnlp file to build/libs folder
  • switched to build/libs folder and started by javaws Test.jnlp

The output from the Java web start client:
Result

Sum up

I used the newest Jersey dependencies from maven central. The test did work, every request call did fetch fresh data from REST service (even when caching was enabled on server side). I tested with Java Webstart "icedtea-web 1.8 (1.8)" on a Linux machine.

Maybe your libraries are outdated and you should simply update your dependencies as in my test files? You could simply try out the examples below with your javaws installation and adopt necessary parts.

If the examples do also produce your problems the root cause seems to be your webstart /Java installation - maybe an update will help.

Sources

JaxRSTestApplication + Gradle setup

plugins {
    id 'java'
    id 'java-library'
    id 'eclipse'
}

group = 'com.example.demo'
version = '1.0.0'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {

    implementation group: 'org.glassfish.jersey.core', name: 'jersey-client', version: '3.0.2'
    implementation group: 'org.glassfish.jersey.inject', name: 'jersey-hk2', version: '3.0.2'
    implementation group: 'jakarta.activation', name: 'jakarta.activation-api', version: '2.0.1'

}

task fatJar(type: Jar) {
    archiveClassifier = 'fat'
    
    duplicatesStrategy = 'exclude'

    from sourceSets.main.output
    
    manifest {
        attributes 'Main-Class': 'com.example.demo.JaxRSTestApplication'
        attributes 'Application-Name': 'JaxRSTestApplication'
    }

    dependsOn configurations.runtimeClasspath
    from {
        configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) }
    }
}
package com.example.demo;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;

import org.glassfish.jersey.client.filter.EncodingFilter;
import org.glassfish.jersey.message.GZipEncoder;

import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Response;

public class JaxRSTestApplication {

    private Client client;
    private WebTarget webTarget;
    private JTextArea textArea;

    public static void main(String[] args) {
        new JaxRSTestApplication().start();
    }

    private class FetchDataAction extends AbstractAction{
        
        private static final long serialVersionUID = 1L;

        private FetchDataAction() {
            putValue(Action.NAME, "fetch data");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            fetchData();
        }
        
    }
    
    private void start() {
        JFrame frame = new JFrame();
        textArea = new JTextArea();
        JButton reloadButton = new JButton(new FetchDataAction());
        frame.add(textArea,BorderLayout.CENTER);
        frame.add(reloadButton, BorderLayout.SOUTH);

        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setSize(new Dimension(400,400));
        frame.setVisible(true);
        
        this.client = ClientBuilder.newBuilder().build();
        client.register(GZipEncoder.class);
        client.register(EncodingFilter.class);

        this.webTarget = client.target("http://localhost:8080/simple");

        fetchData();
        fetchData();
    }

    private void fetchData() {
        // Get Method
        Response response = this.webTarget.request().get();
        // Extract the content
        String result = response.readEntity(String.class);
        textArea.setText(textArea.getText()+"\n"+"read:" + result);

        response.close();

    }
}

JNLP file

<?xml version="1.0" encoding="utf-8"?> 
<jnlp spec="1.0+" codebase="./">
    <information>
        <title>Jnlp Testing</title>
        <vendor>de-jcup</vendor>
        <homepage href="http://localhost:8080/" />
        <description>Just a test application</description>
    </information>
    <security>
        <all-permissions/>
    </security>
    <resources>
        <j2se version="1.8+" />
        <jar href="demo-1.0.0-fat.jar" />
    </resources>
    <application-desc main-class="com.example.demo.JaxRSTestApplication" />
</jnlp>

Spring REST demo service + Gradle setup

plugins {
    id 'org.springframework.boot' version '2.6.0-SNAPSHOT'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
    id 'java-library'
    id 'eclipse'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
    maven { url 'https://repo.spring.io/milestone' }
    maven { url 'https://repo.spring.io/snapshot' }
}

dependencies {
    api 'org.springframework.boot:spring-boot-starter-web'
}
package com.example.springrestdemo;

import java.util.concurrent.TimeUnit;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.CacheControl;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class SpringRestDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringRestDemoApplication.class, args);
    }
    
    @RestController
    public class SimpleController {

        private long secondWeWantTobeCached = 3600;

        @GetMapping(value = "/simple", produces = MediaType.APPLICATION_JSON_VALUE)
        public ResponseEntity<StringJsonObject> simpleResult() {
            return ResponseEntity.ok().cacheControl(CacheControl.maxAge(secondWeWantTobeCached, TimeUnit.SECONDS).cachePublic().noTransform())
                    .body(new StringJsonObject("Timestamp:" + System.currentTimeMillis()));
        }

        public class StringJsonObject {

            private String content;

            public StringJsonObject(String content) {
                this.content = content;
            }

            public String getContent() {
                return content;
            }
        }
    }

}

Show exactly how you set Cache-Control directives in your client. This's important because there're many directives and their combinations. Read the specification carefully because the directive names don't always convey how their mechanisms really work.

Have you used directives no-store or no-cache?

Related