Commit f4b53150 authored by Andre Blanke's avatar Andre Blanke
Browse files

Use memcached for caching URL mappings with xmemcached

parent c7d7f9f8
[Service]
ExecStart=
# Leave off usage of the $LISTEN environment variable set by the distribution-
# supplied memcached.service file, as it only binds to 127.0.0.1 and ::1 by
# default.
# Instead we use memcached's default binding by not supplying the "-l" option
# which means we bind to INETADDR_ANY instead.
ExecStart=/usr/bin/memcached -m ${CACHESIZE} -c ${MAXCONN} $OPTIONS
......@@ -2,6 +2,7 @@
Description=upbshrt.xyz backend
[Service]
Environment=MEMCACHED_CLIENTS="192.168.10.11:11211"
ExecStart=/usr/bin/java -jar /opt/shortener/shortener.jar
[Install]
......
......@@ -25,6 +25,7 @@
create: yes
validate: 'visudo --check --file %s'
# TODO: Check if this is still needed
- name: Add shortener user
user:
name: shortener
......@@ -52,6 +53,24 @@
state: started
enabled: yes
- name: Install memcached
package:
name: memcached
state: present
- name: Create /etc/systemd/system/memcached.service.d/ directory
file:
path: /etc/systemd/system/memcached.service.d/
state: directory
- name: Copy override.conf override file to /etc/systemd/system/memcached.service.d/
template:
src: /vagrant/ansible/roles/worker/files/systemd/system/memcached.service.d/override.conf
dest: /etc/systemd/system/memcached.service.d/override.conf
- name: Start and enable memcached.service
systemd:
name: memcached
state: started
enabled: yes
- name: Install OpenJDK 15
package:
name: jdk-openjdk
......
......@@ -68,6 +68,12 @@
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.googlecode.xmemcached</groupId>
<artifactId>xmemcached</artifactId>
<version>2.4.7</version>
</dependency>
<!-- <editor-fold desc="Test dependencies" -->
<dependency>
<groupId>org.springframework.boot</groupId>
......
package xyz.upbshrt.shortener
import net.rubyeye.xmemcached.MemcachedClient
import net.rubyeye.xmemcached.XMemcachedClientBuilder
import net.rubyeye.xmemcached.utils.AddrUtil
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
......@@ -13,4 +18,8 @@ class ShortenerApplication {
@Bean
fun allowedUrlProtocols() = setOf("http", "https")
@Bean
fun memcachedClient(@Value("#{systemEnvironment['MEMCACHED_CLIENTS']}") memcachedClients: String): MemcachedClient =
XMemcachedClientBuilder(AddrUtil.getAddresses(memcachedClients)).build()
}
......@@ -5,10 +5,14 @@ import java.net.URLDecoder
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.math.BigInteger
import java.util.*
import javax.validation.constraints.Pattern
import net.rubyeye.xmemcached.MemcachedClient
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
......@@ -26,9 +30,19 @@ import xyz.upbshrt.shortener.repository.UrlMappingRepository
@Validated
@RestController
class UrlShorteningController @Autowired constructor(private val repository: UrlMappingRepository,
class UrlShorteningController @Autowired constructor(private val repository: UrlMappingRepository,
private val memcachedClient: MemcachedClient,
private val allowedUrlProtocols: Set<String>) {
/**
* The expiration time for memcached entries inserted from this controller taken from the
* `MEMCACHED_EXPIRATION_TIME` environment variable.
*
* Defaults to an expiration time of three hours if that variable is unset.
*/
@Value("#{systemEnvironment['MEMCACHED_EXPIRATION_TIME'] ?: 3 * 60 * 60}")
private var memcachedExpirationTime: Int? = null
private fun handleMalformedId(id: String): Nothing =
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "'$id' is not a valid id for linking to a URL.")
......@@ -88,6 +102,19 @@ class UrlShorteningController @Autowired constructor(private val repository: Url
try {
val urlMapping: UrlMapping = repository.save(UrlMapping(numericId, uri.toString()))
try {
/*
* We use the decimal representation of the id as a key for the memcached entry to be consistent with
* the database representation of UrlMapping which also stores the key as a regular integer.
*/
memcachedClient.set(
urlMapping.id!!.toString(),
memcachedExpirationTime!!,
urlMapping.url
)
} catch (ignored: Exception) {}
return ResponseEntity.ok(URLEncoder.encode(urlMapping.toString(), StandardCharsets.UTF_8))
} catch (exception: NumberFormatException) {
handleMalformedId(id!!)
......@@ -112,16 +139,25 @@ class UrlShorteningController @Autowired constructor(private val repository: Url
handleMalformedId(id)
}
return repository
.findById(numericId)
.map {
ResponseEntity
.status(HttpStatus.FOUND)
.location(URI(it.url))
.build<Nothing>()
}
.orElseThrow {
ResponseStatusException(HttpStatus.NOT_FOUND, "No mapped URL found for id '$numericId'.")
}
val targetUrl: Optional<String> = try {
/*
* Again use the base 10 representation instead of that of UrlMapping.ID_RADIX for consistency with the
* database.
*/
memcachedClient.get(numericId.toString())
} catch (exception: Exception) {
repository
.findById(numericId)
.map { it.url }
}
return targetUrl.map {
ResponseEntity
.status(HttpStatus.FOUND)
.location(URI(it))
.build<Nothing>()
}.orElseThrow {
ResponseStatusException(HttpStatus.NOT_FOUND, "No mapped URL found for id '$id'.")
}
}
}
......@@ -27,7 +27,7 @@ class UrlMapping(id: BigInteger?, url: String) {
Parameter(name = SequenceStyleGenerator.INCREMENT_PARAM, value = "1")
]
)
private var id: BigInteger? = id
var id: BigInteger? = id
@Column(length = 2048, nullable = false)
var url: String = url
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment