How to setup collaborative (parallel) editing in Atlassian Confluence

Written by - 0 comments

Published on August 18th 2017 - Listed in Linux Internet Atlassian


It's been a while since I focused on Confluence (see article "Tackling Confluence migration issues (Windows+MSSQL to Linux+MariaDB" from March 2017). Now it was time to upgrade the migrated Confluence (5.7) to a new version (6.3.3 as of this writing).
Since Confluence version 6 it is possible to edit a document (page) in a collaborative manner. This means users Dave and Eddy can be working on the same page at the same time and they can even see the changes in realtime. Sounds pretty cool, right?

In order to use this new feature, which is technically called "Synchrony", some changes will happen to your Confluence installation. Check out the official documentation, too.

  • The Context path of Tomcat must be changed from "" to "/confluence".
  • Due to the changed Context path, the Confluence URL changes (append /confluence).
  • Due to the changed Confluence URL, the base URL must be changed in the Confluence settings (can be done in UI).
  • Next to the main Tomcat process, a second process will be started by Confluence. This second process will listen with tcp port 8091 (so make sure you're not using that port on that machine!).
  • Requests to /confluence have to go to the main Tomcat (default listener tcp port 8090).
  • Requests to /synchrony have to go to the second process (port 8091).
  • Requests to /synchrony need additional http headers.
  • Synchrony does not speak SSL/HTTPS. Therefore, if you use https, you must go through a reverse proxy.

Putting all this information together, let's revise it technically.

Change the Context path

To separate "normal" Confluence and "dynamic" Synchrony requests (which uses web sockets), the paths must be split up into /confluence and /synchrony. The main Tomcat server (which runs Confluence) must be run as /confluence. To do that, edit the server.xml (default path: /opt/atlassian/confluence/conf/server.xml) and adapt the Context path (right after the Host snippet):

<!-- <Context path="" docBase="../confluence" debug="0" reloadable="false" useHttpOnly="true"> -->
<Context path="/confluence" docBase="../confluence" debug="0" reloadable="false" useHttpOnly="true">
<!-- Logging configuration for Confluence is specified in confluence/WEB-INF/classes/log4j.properties -->
  <Manager pathname=""/>
  <Valve className="org.apache.catalina.valves.StuckThreadDetectionValve" threshold="60"/>
</Context>

<Context path="${confluence.context.path}/synchrony-proxy" docBase="../synchrony-proxy" debug="0" reloadable="false" useHttpOnly="true">
  <Valve className="org.apache.catalina.valves.StuckThreadDetectionValve" threshold="60"/>
</Context>

Oh and while we're at it and I'm pretty sure you run a Reverse Proxy in front of Confluence like I do, don't forget to add the proxy parameters to your Connector snippet (also in server.xml at the top):

                maxThreads="200" minSpareThreads="10"
                enableLookups="false" acceptCount="10" debug="0" URIEncoding="UTF-8"
                proxyName="wiki-test.example.com" proxyPort="443" scheme="https" secure="true" />

Restart Confluence after the changes:

root@inf-wiki01-t:~# /etc/init.d/confluence restart

--------------------------------- ---------------------------------

Adapt Reverse Proxy config

As I wrote before, there are now two paths to consider: /confluence and /synchrony. This must be handled in the Reverse Proxy; the upstreams for these paths are now on different ports.
Also Confluence's Base URL has changed due to the adapted Context path, therefore I changed the "location /" into a general rewrite (redirect) to add the path "/confluence". This is especially helpful for direct links/bookmarks using the old Base URL.
I mentioned additional http headers before. You can see them in the "location /synchrony".
Here's my full Reverse Proxy config (obviously Nginx):

server {
  listen 80;
  server_name wiki-test.example.com;
  access_log /var/log/nginx/wiki-test.example.com.access.log;
  error_log /var/log/nginx/wiki-test.example.com.error.log;

  location / {
    rewrite ^(.*) https://wiki-test.example.com$1 permanent;
  }
}

server {
  listen 443;
  server_name wiki-test.example.com;
  access_log /var/log/nginx/wiki-test.example.com.access.log;
  error_log /var/log/nginx/wiki-test.example.com.error.log;

  ssl on;
  ssl_certificate /etc/nginx/ssl.crt/wildcard.example.com.crt;
  ssl_certificate_key /etc/nginx/ssl.key/wildcard.example.com.key;
  ssl_session_timeout 5m;
  ssl_prefer_server_ciphers on;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers ALL:!aNULL:!eNULL:!LOW:!EXP:!RC4:!3DES:+HIGH:+MEDIUM;
  ssl_dhparam /etc/ssl/private/dh2048.pem;

  error_page   500 502 503 504  /50x.html;
  error_page   403  /403.html;
  error_page   404  /404.html;

  location = /50x.html {
      root   /usr/share/nginx/html;
  }

  location = /403.html {
      root   /usr/share/nginx/html;
  }

  location = /404.html {
      root   /usr/share/nginx/html;
  }

  location / {
    rewrite ^(.*) https://wiki-test.example.com/confluence$1 permanent;
  }

  location /confluence {
    include /etc/nginx/proxy.conf;
    proxy_pass http://inf-wiki01-t.example.com:8090/confluence;
  }

  location /synchrony {
    include /etc/nginx/proxy.conf;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_pass http://inf-wiki01-t.example.com:8091/synchrony;
  }
}

For the sake of completeness, here's the proxy.conf as well:

proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 200m;
client_body_buffer_size 128k;
proxy_connect_timeout 6000;
proxy_send_timeout 6000;
proxy_read_timeout 6000;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
send_timeout 6000;
proxy_buffering off;
proxy_next_upstream error;

Needless to say, these changes require a Nginx reload.

Now that the necessary changes are done, let's try this out!

On the systems running Confluence, the successful start of Synchrony can be checked in the Tomcat log files right after starting Confluence:

2017-08-18 11:55:41,195 INFO [ListenableFutureAdapter-thread-1] [plugins.synchrony.bootstrap.DefaultSynchronyProcessManager] updateSynchronyConfiguration Synchrony Internal Service URL: http://127.0.0.1:8091/synchrony/v1

The listener 8091 is then up:

root@inf-wiki01-t:~# netstat -lntup | grep java
tcp6       0      0 :::8090                 :::*                    LISTEN      13608/java     
tcp6       0      0 :::8091                 :::*                    LISTEN      14346/java     
tcp6       0      0 127.0.0.1:8000          :::*                    LISTEN      13608/java  

As you can already see in netstat, the process ID is different than the one from the main Tomcat process. In detail:

conflue+ 13608 14.7 30.7 8425044 1878852 ?     Sl   13:53   9:32 /opt/atlassian/confluence/jre//bin/java -Djava.util.logging.config.file=/opt/atlassian/confluence/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dconfluence.context.path=/confluence -Datlassian.plugins.startup.options= -Dorg.apache.tomcat.websocket.DEFAULT_BUFFER_SIZE=32768 -Dsynchrony.enable.xhr.fallback=true -Xms1024m -Xmx4096m -XX:+UseG1GC -Datlassian.plugins.enable.wait=300 -Djava.awt.headless=true -XX:G1ReservePercent=20 -Xloggc:/opt/atlassian/confluence/logs/gc-2017-08-18_13-53-42.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=2M -XX:-PrintGCDetails -XX:+PrintGCDateStamps -XX:-PrintTenuringDistribution -Djava.endorsed.dirs=/opt/atlassian/confluence/endorsed -classpath /opt/atlassian/confluence/bin/bootstrap.jar:/opt/atlassian/confluence/bin/tomcat-juli.
conflue+ 14346  1.8 11.7 4704556 715768 ?      Sl   13:57   1:08  \_ /opt/atlassian/confluence/jre/bin/java -classpath /opt/atlassian/confluence/temp/1.0.0-release-confluence_6.1-78073294.jar:/opt/atlassian/confluence/lib/mysql-connector-java-5.1.41-bin.jar -Xss2048k -Xmx1g synchrony.core sql

Now using the browser and logging into Confluence, the first apparent thing is a warning popping up about the changed Base URL:

Confluence Base URL changed after Synchrony

Obviously, the Base URL needs to be changed (append /confluence).

In the Administration part, there's a new page called "Collaborative editing". Everything on that page should be green and "Running". Otherwise you have to start troubleshooting.

Confluence Synchrony

Note: During my first try I got errors on that page saying "The Synchrony service is stopped or has errors". This was because I had previously run the main Tomcat with two listeners (8090 for proxied connections, 8091 for non-proxied). After disabling the second Tomcat connector/listener and therefore freeing up port 8091 the errors were gone.

Time for making the collaborative editing test! I went on a page and saw in the top right corner there's a new icon:

Confluence Collaborative Editing

So that's obviously me. The Plus-icon allows to invite additional users to work with you on the same page. Shortly after that, my colleague arrived on the page and I saw his avatar appearing.

Confluence Collaborative Editing

So we started working. I was able to see in real-time in which part of the document my colleague was (indicated with a colored flag) and what he's been adding so far:

Confluence Collaborative Editing

Yes! This is pretty cool and it's working! Thumbs up to Atlassian!

The only negative point is that the Confluence's URL changes as I mentioned before. But with the automatic redirect in the Reverse Proxy, this can be handled properly. Maybe Atlassian will one day merge the main process Confluence and Synchrony so a Base URL change wouldn't be necessary anymore.


Add a comment

Show form to leave a comment

Comments (newest first)

No comments yet.