In Confluence 9.3.1 REST requests don't work with our plugin installed, but in Confluence < 9.3.1 e.g. in 9.2.1 version all worked well

Hello, our plugin track REST system events and intercept related REST requests. Specifically in Confluence 9.3.1 with our plugin installed there is a problem that all these Confluence REST requests that we intercept do not work. And for these requests we have
400 bad request status in response and also this message in response e.g. for case when we backup site:
"Trailing token (of type START_OBJECT) found after value (bound as com.atlassian.confluence.api.model.backuprestore.SiteBackupSettings): not allowed as per DeserializationFeature.FAIL_ON_TRAILING_TOKENS

at [Source: REDACTED (StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION disabled); line: 1, column: 69]"

But in Confluence < 9.3.1 e.g. in 9.2.1 version all worked well.

Could you please explain how to fix this problem? It is very important and urgent for us.

Did you solve the problem already? We have a similar issue and I’m pretty certain that this is caused by improper wrapping of the request being intercepted by the filter. I tried quite a few variations different to the wrapper we are currently using but with no success so far.

Yes, I solved the problem using my own CompatibilityRequestWrapper

    HttpServletRequestWrapper wrappedRequest;
    try {
      wrappedRequest = new RestReadingServletRequest((HttpServletRequest) request);
      if (isConfluenceApplication) {
        wrappedRequest = new CompatibilityRequestWrapper(wrappedRequest);
      }
    } catch (Exception e) {
      log.error(e);
      chain.doFilter(request, response);
      return;
    }

    chain.doFilter(wrappedRequest, response);

RestReadingServletRequest have logic to create multi-readable request that reads request body to temp var and overrides getReader and getInputStream to return this temp stored data


import java.io.BufferedReader;
import java.io.IOException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/**
 * This class in needed to fix error in Confluence 9.3.1: "Trailing token (of type START_OBJECT)
 * found after value: not allowed as per `DeserializationFeature.FAIL_ON_TRAILING_TOKENS`" error.
 */
public class CompatibilityRequestWrapper extends HttpServletRequestWrapper {

  private static final FormattingLogger log =
      FormattingLogger.getLogger(CompatibilityRequestWrapper.class);

  private CompatibilityInputStream compatibilityInputStream;
  private CompatibilityBufferedReader compatibilityBufferedReader;

  public CompatibilityRequestWrapper(HttpServletRequest httpServletRequest) {
    super(httpServletRequest);
  }

  public ServletInputStream getInputStream() throws IOException {
    if (this.compatibilityInputStream == null) {
      this.compatibilityInputStream = new CompatibilityInputStream(super.getInputStream());
    }
    return this.compatibilityInputStream;
  }

  public BufferedReader getReader() throws IOException {
    if (this.compatibilityBufferedReader == null) {
      this.compatibilityBufferedReader = new CompatibilityBufferedReader(super.getReader());
    }

    return this.compatibilityBufferedReader;
  }

  private class CompatibilityInputStream extends ServletInputStream {
    private final ServletInputStream delegate;

    private CompatibilityInputStream(ServletInputStream delegate) {
      this.delegate = delegate;
    }

    @Override
    public int read() throws IOException {
      return this.delegate.read();
    }

    @Override
    public int read(byte[] b) throws IOException {
      return this.delegate.read(b);
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
      return this.delegate.read(b, off, len);
    }

    @Override
    public int readLine(byte[] b, int off, int len) throws IOException {
      return this.delegate.readLine(b, off, len);
    }

    @Override
    public long skip(long n) throws IOException {
      return this.delegate.skip(n);
    }

    @Override
    public int available() throws IOException {
      return this.delegate.available();
    }

    @Override
    public void close() throws IOException {
      this.delegate.close();
    }

    @Override
    public void mark(int readlimit) {
      this.delegate.mark(readlimit);
    }

    @Override
    public void reset() throws IOException {
      this.delegate.reset();
    }

    @Override
    public boolean markSupported() {
      return this.delegate.markSupported();
    }
  }

  private class CompatibilityBufferedReader extends BufferedReader {
    private final BufferedReader delegate;

    private CompatibilityBufferedReader(BufferedReader delegate) {
      super(delegate);
      this.delegate = delegate;
    }

    @Override
    public int read() throws IOException {
      return this.delegate.read();
    }

    @Override
    public String readLine() throws IOException {
      return this.delegate.readLine();
    }

    @Override
    public int read(char[] cbuf) throws IOException {
      return this.delegate.read(cbuf);
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
      return this.delegate.read(cbuf, off, len);
    }

    @Override
    public long skip(long n) throws IOException {
      return this.delegate.skip(n);
    }

    @Override
    public boolean ready() throws IOException {
      return this.delegate.ready();
    }

    @Override
    public boolean markSupported() {
      return this.delegate.markSupported();
    }

    @Override
    public void mark(int readAheadLimit) throws IOException {
      this.delegate.mark(readAheadLimit);
    }

    @Override
    public void reset() throws IOException {
      this.delegate.reset();
    }

    @Override
    public void close() throws IOException {
      this.delegate.close();
    }
  }
}

It’s great to hear that you fixed it and I’m just trying to adopt the same changes. I have question though: I’m getting “Class ‘CompatibilityInputStream’ must either be declared abstract or implement abstract methods ‘isFinished(), isReady() and setReadListener()’ in ‘ServletInputStream’”. I’m wondering why you don’t need to have these. I can see that ServletInputStream comes from javax.servlet-api:3.1.0. What’s your version?

Also, if feasible, it would be great to see your full RestReadingServletRequest class. Just in case combining your CompatibilityRequestWrapper with our original RequestWrapper won’t work.

Lastly: I highly appreciate replying and providing all these details :tada:

It’s great to hear that you fixed it and I’m just trying to adopt the same changes. I have question though: I’m getting “Class ‘CompatibilityInputStream’ must either be declared abstract or implement abstract methods ‘isFinished(), isReady() and setReadListener()’ in ‘ServletInputStream’”. I’m wondering why you don’t need to have these. I can see that ServletInputStream comes from javax.servlet-api:3.1.0. What’s your version?

I think you can try to add implementation of missing methods if you use sevlet api with higher versions
I used this dependency i.e. servlet api version is 2.3

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.3</version>
            <scope>provided</scope>
        </dependency>

Also, if feasible, it would be great to see your full RestReadingServletRequest class. Just in case combining your CompatibilityRequestWrapper with our original RequestWrapper won’t work.


import org.apache.log4j.Logger;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class RestReadingServletRequest extends HttpServletRequestWrapper {
  private static final Logger log =
      Logger.getLogger(RestReadingServletRequest.class);
  private final byte[] body;

  public RestReadingServletRequest(HttpServletRequest request) throws IOException {
    super(request);
    BufferedInputStream bis = null;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try {
      InputStream inputStream = request.getInputStream();
      if (inputStream != null) {
        bis = new BufferedInputStream(inputStream);
        byte[] byteBuffer = new byte[1024];
        int bytesRead = -1;
        while ((bytesRead = bis.read(byteBuffer)) > 0) {
          baos.write(byteBuffer, 0, bytesRead);
        }
      }
    } catch (IOException ex) {
      log.error(String.format("Error reading request input stream: %s", ex.getMessage()));
      throw ex;
    } finally {
      if (bis != null) {
        try {
          bis.close();
          baos.close();
        } catch (IOException ex) {
          log.error(String.format("Error closing request input stream: %s", ex.getMessage()));
          throw ex;
        }
      }
    }
    body = baos.toByteArray();
  }

  @Override
  public ServletInputStream getInputStream() throws IOException {
    final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
    ServletInputStream servletInputStream = new ServletInputStream() {
      public int read() throws IOException {
        return byteArrayInputStream.read();
      }
    };
    return servletInputStream;
  }

  @Override
  public BufferedReader getReader() throws IOException {
    return new BufferedReader(new InputStreamReader(this.getInputStream()));
  }

  public String getBody() {
    try {
      return new String(body, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      return new String(body);
    }
  }
}


1 Like

Awesome. Let me work with that a bit and see if I can fix my problem. Again, I highly appreciate your feedback. I’m sure this thread will be(come) interesting for other P2 developers as well.
Especially given the fact that this problem only started with recent versions of Jira and Confluence.

this problem only started with recent versions of Jira

In which Jira version this problem also exist? I reproduced this problem only in Confluence 9.3.1. I didn’t reproduce this problem on Jira 10.4. But I haven’t tested yet is this problem reproduced on Jira 10.5. Problem exist on Jira 10.5 or also in earlier Jira versions? Could you please provide Jira version on which you reproduced the problem? Could you please also provide requests on which this problem reproduced in Jira?

In Jira Software 10.4.0 everything worked as it did for 2+ years.

It changed with 10.4.1.
We intercept a request for the /rest/tsv/1.0/authenticate endpoint that belongs to the new login form (which also exists in Confluence). But I only need to intercept it in Jira and I’m sure the same would happen in Confluence if I had to.

If you enter your username and password in the new login form, the data is POSTed to that endpoint.

And to be more precise: We also need to read (the username) from the request payload.

There’s still a bit of testing required (with older Jira version etc.) but at least it works already in my Jira Software 10.5.0. This ends a few-days journey of trying multiple things and I’m more than happy. :star:

OK, thank you for your replies, will we check does problem exist also on Jira on our side for requests that are relevant for us.

Thank you!