Skip to content

Commit

Permalink
Merge pull request #556 from acelaya-forks/feature/shlink-4
Browse files Browse the repository at this point in the history
Feature/shlink 4
  • Loading branch information
acelaya authored Mar 4, 2024
2 parents 301b2e5 + 23840c0 commit 66c8026
Show file tree
Hide file tree
Showing 22 changed files with 225 additions and 291 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).

## [7.5.0] - 2024-03-04
### Added
* Update documentation to cover changes from Shlink 4.0.0

### Changed
* Update dependencies

### Deprecated
* *Nothing*

### Removed
* *Nothing*

### Fixed
* *Nothing*


## [7.4.0] - 2024-01-30
### Added
* Update documentation around shlink-web-client, to reflect changes from v4.0.0
Expand Down
5 changes: 3 additions & 2 deletions config/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ server {
rewrite ^/documentation/(multiple-domains|import-short-urls|real-time-updates)/?$ /documentation/advanced/$1/ permanent;
rewrite ^/api-docs/(.*)$ /documentation/api-docs/$1/ permanent;
rewrite ^/command-line-interface/?$ /documentation/command-line-interface/ permanent;
rewrite ^/documentation/serve-with-swoole/?$ /documentation/serve-with-openswoole/ permanent;
rewrite ^/documentation/serve-with-openswoole/?$ /documentation/supported-runtimes/serve-with-openswoole/ permanent;
rewrite ^/documentation/serve-with-swoole/?$ /documentation/supported-runtimes/ permanent;
rewrite ^/documentation/serve-with-openswoole/?$ /documentation/supported-runtimes/ permanent;
rewrite ^/documentation/supported-runtimes/serve-with-openswoole/?$ /documentation/supported-runtimes/ permanent;
rewrite ^/documentation/classic-web-server/?$ /documentation/supported-runtimes/classic-web-server/ permanent;
rewrite ^/swagger-ui(.*)$ https://api-spec.shlink.io permanent;

Expand Down
2 changes: 1 addition & 1 deletion src/components/Terminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const strings: IStrings = {
+ '<br>&nbsp;&nbsp;<span class="black">"shortUrl"</span>: <span class="green">"https://s.test/rY9k"</span>,'
+ '<br>&nbsp;&nbsp;<span class="black">"longUrl"</span>: <span class="green">"https://shlink.io"</span>,'
+ '<br>&nbsp;&nbsp;<span class="black">"dateCreated"</span>: <span class="green">"2016-05-02T17:49:53+02:00"</span>,'
+ '<br>&nbsp;&nbsp;<span class="black">"visitsCount"</span>: 0,'
+ '<br>&nbsp;&nbsp;<span class="black">"visitsSummary"</span>: {},'
+ '<br>&nbsp;&nbsp;<span class="black">"tags"</span>: [],'
+ '<br>&nbsp;&nbsp;<span class="black">"meta"</span>: {}'
+ '<br>}`',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
layout: ../../../layouts/DocsLayout.astro
---

import { Callout } from '../../../components/Callout';

## Dynamic rule-based redirects system

Shlink 4.0.0 introduced a new system to dynamically redirect visitors to different long URLs based on runtime conditions.

It works by allowing you to define any number of rules for a short URL, which are checked sequentially based on their priority, until one matches.

If a rule matches, the visitor is redirected to that rule's long URL, and only if none match, they are redirected to the default long URL normally.

Every rule can be composed of any number of conditions, but all of them need to match in order to consider the rule a positive match.

### Supported conditions

Currently, Shlink supports the next condition types:

* **Language**: Checks the `Accept-Language` header in order to see if one specific language is accepted.
* **Query param**: Verifies if the query string contains a specific query param with a specific value.
* **Device**: Tries to match the visitor's device based on the `User-Agent` header, and infer if it's `Android`, `iOS` or `Desktop`. Other devices like `Tablet`, `iPad` etc. could be added in the future.

<Callout type="info">
Device rules used to be handled separately, but they were migrated to the new rule system for consistency and flexibility.
</Callout>

### Handling redirect rules

There are mainly two ways to manage dynamic redirect rules for a specific short URL:

* The REST API, via [POST /short-urls/[shortCode]/redirect-rules](https://api-spec.shlink.io/?version=v4.0.0#/Redirect%20rules/setShortUrlRedirectRules) endpoint, which expects a body like this:
```json
{
"redirectRules": [
{
"longUrl": "https://example.com/android-en-us",
"conditions": [
{
"type": "device",
"matchValue": "android",
"matchKey": null
},
{
"type": "language",
"matchValue": "en-US",
"matchKey": null
}
]
},
{
"longUrl": "https://example.com/fr",
"conditions": [
{
"type": "language",
"matchValue": "fr",
"matchKey": null
}
]
},
{
"longUrl": "https://example.com/query-foo-bar-hello-world",
"conditions": [
{
"type": "query",
"matchKey": "foo",
"matchValue": "bar"
},
{
"type": "query",
"matchKey": "hello",
"matchValue": "world"
}
]
}
]
}
```
* The command line interface, via `short-code:manage-rules` command:
```shell
$ shlink short-url:manage-rules rmD8f
---------- ------------------- -----------------------------
Priority Conditions Redirect to
---------- ------------------- -----------------------------
1 device is android https://example.com/android
2 device is desktop https://example.com
---------- ------------------- -----------------------------


What do you want to do next? [Save and exit]:
[0] Add new rule
[1] Remove existing rule
[2] Re-arrange rule
[3] Save and exit
[4] Discard changes
>
```


### FAQs

* **Can I use the same type of condition more than once in the same rule?**

Yes. Of course, this does not always make sense, like a visitor will never be using an `Android` and `iOS` device at the same time, but you could check for more than one query param, or if more than one language is accepted.

* **Can I make a rule match as soon as some of its conditions match?**

No. In order to match a rule, all its conditions need to positively match. There's no way to configure a partial match.

However, this behavior can be easily mimicked by defining consecutive rules that redirect to the same long URL.

For example, if you want to redirect to `https://example.com/foo` when visitors are (`Android` AND `?foo=bar`) OR (`iOS` AND `?hello=world`), create one rule for the first two conditions, and another rule for the other two, and point both of them to `https://example.com/foo`.

* **How granular is the language condition?**

The Language condition allows for a lot of flexibility, as you can match full locales (in the form of `en_US` or `en-US`) but also language codes.

For example, if you define the rule with value `es-ES` and the header is sent like `Accept-Language: fr-FR, es-ES, en-UK`, it will match positively, however, it won't match `Accept-Language: es`.

On the other hand, if the rule is defined as only `es`, it will match in both cases, as it means "any Spanish".

There's one exception. If the `Accept-Language` header is sent with value `*`, it will be ignored and never match, as it would incorrectly match any language otherwise.

Finally, it's worth mentioning language matches are case-insensitive.
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ export const components = markdownComponents

## Exposing Shlink through a reverse proxy

When you are serving Shlink with openswoole or using the openswoole-based docker image, you'll probably want to put a reverse proxy in front of it.
When you are serving Shlink with RoadRunner or using the docker image, you'll probably want to put a reverse proxy in front of it.

Openswoole have some limitations, that can be solved by using a reverse proxy:
RoadRunner has some limitations, that can be solved by using a reverse proxy:

* Using a standard HTTP port (80, 443), so that users don't have to explicitly set the port in the URL.
* Using an `https` certificate to encrypt the connection, keeping your users' data safe.

It is possible to achieve both just by tweaking openswoole, but instead, it is recommended to put a standard HTTP server in front of it, like nginx or apache, as openswoole implements only a subset of the HTTP standard.

The only consideration to ensure all Shlink capabilities work as expected, is that you make sure the consumer's IP address, and the request's domain, are forwarded from the proxy to Shlink.

### Configure the proxy
Expand All @@ -26,7 +24,7 @@ Here you can find some docs on how to do it with the most used web servers:
* Apache: https://httpd.apache.org/docs/trunk/en/howto/reverse_proxy.html
* Caddy: https://caddyserver.com/docs/quick-starts/reverse-proxy

Example nginx configuration to use as a reverse proxy in front of openswoole, with SSL enabled:
Example nginx configuration to use as a reverse proxy in front of RoadRunner, with SSL enabled:

```nginx
server {
Expand Down
59 changes: 1 addition & 58 deletions src/pages/documentation/advanced/real-time-updates.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ Currently, Shlink supports the next technologies to publish updates.
* [Mercure hub](/documentation/advanced/real-time-updates#mercure-hub-server): allows to subscribe to server-sent events. *(Since v2.2.0)*.
* [RabbitMQ](/documentation/advanced/real-time-updates#rabbitmq-server): to publish server-to-server updates *(Since v2.10.0)*.
* [Redis pub/sub](/documentation/advanced/real-time-updates#redis-pub-sub): to publish server-to-server updates *(Since v3.2.0)*.
* [Webhooks](/documentation/advanced/real-time-updates#webhooks): will be called with new visits.

<Callout type="warning">
Real-time updates only work when [openswoole](/documentation/supported-runtimes/serve-with-openswoole/) or [RoadRunner](/documentation/supported-runtimes/serve-with-roadrunner/), which is the case if you are using the <Link href="/documentation/install-docker-image">docker image</Link>.
Real-time updates only work with [RoadRunner](/documentation/supported-runtimes/serve-with-roadrunner/), which is the case if you are using the <Link href="/documentation/install-docker-image">docker image</Link>.
</Callout>

### Topics and payloads
Expand Down Expand Up @@ -102,59 +101,3 @@ The exchanges and queues are created in a "durable" way, with "direct" publishin
Since Shlink v3.2.0, if you configured a redis server/cluster for caching, Shlink can also use that instance for real-time updates via redis pub/sub.

When this happens, the redis server/cluster will act as an intermediary where Shlink publishes the updates, and others can subscribe to them.

### Webhooks

<Callout type="warning">
As of Shlink 3.1.0, webhooks are considered <b>deprecated</b>, since any new kind of update is by definition a breaking change, and they provide less flexibility than standard queuing systems.

Because of this, only visits and, optionally, orphan visits, will be notified to webhooks.

For a fully featured, real-time subscription to Shlink events, consider using any of the other options.
</Callout>

Shlink supports providing a comma-separated list of endpoints, via [env vars](/documentation/environment-variables#webhooks-integration) or [installation tool](/documentation/command-line-interface/installation-tool), which will receive a `POST` request from Shlink when a short URL is visited.

The body of the request will contain the payload of both the `shortUrl` that was visited, and the `visit` with the geolocation, like this:

```json
{
"shortUrl": {
"shortCode": "12C18",
"shortUrl": "https://s.test/12C18",
"longUrl": "https://store.steampowered.com",
"dateCreated": "2016-08-21T20:34:16+02:00",
"visitsCount": 328,
"tags": [
"games",
"tech"
],
"meta": {
"validSince": "2017-01-21T00:00:00+02:00",
"validUntil": null,
"maxVisits": 100
},
"domain": null
},
"visit": {
"referer": "https://t.co",
"date": "2015-08-20T05:05:03+04:00",
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
"visitLocation": {
"cityName": "Cupertino",
"countryCode": "US",
"countryName": "United States",
"latitude": 37.3042,
"longitude": -122.0946,
"regionName": "California",
"timezone": "America/Los_Angeles"
}
}
}
```

<Callout type="info">
Starting with Shlink v2.9.0, you can also enable sending requests for orphan visits. In that case, the <b>shortUrl</b> prop won't be present.

This option is disabled by default for backwards compatibility reasons, so you need to actively opt-in.
</Callout>
4 changes: 2 additions & 2 deletions src/pages/documentation/advanced/using-redis.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Shlink can take advantage of [redis](https://redis.io/) in multiple contexts.

Shlink can persist certain things in a faster storage for quick access.

By default, it uses APCu if available (when using a [classic web server](/documentation/supported-runtimes/classic-web-server/)) or in-memory (when using [openswoole](/documentation/supported-runtimes/serve-with-openswoole/) or [Roadrunner](/documentation/supported-runtimes/serve-with-roadrunner/) as the runtimes), but it can be provided with some redis access configuration.
By default, it uses APCu if available (when using a [classic web server](/documentation/supported-runtimes/classic-web-server/)) or in-memory (when using [Roadrunner](/documentation/supported-runtimes/serve-with-roadrunner/) as the runtimes), but it can be provided with some redis access configuration.

When doing so, Shlink will use redis as the cache persistence.

Expand All @@ -44,7 +44,7 @@ Shlink supports multiple redis capabilities, depending on how the configuration

<Callout type="warning">
Prior to Shlink 3.7.0, password-only URIs had to have the form `tcp://the_password@my_redis_server:6379`.
Since that's not a valid URI, it is considered deprecated, and will be removed in Shlink 4.0.0
Since that's not a valid URI, it has been removed removed in Shlink 4.0.0
</Callout>

### Providing configuration
Expand Down
2 changes: 1 addition & 1 deletion src/pages/documentation/api-docs/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ When performing an API call, if no API key is provided or it is invalid/disabled

```json
{
"type": "INVALID_API_KEY",
"type": "https://shlink.io/api/error/invalid-api-key",
"detail": "Provided API key does not exist or is invalid",
"title": "Invalid API key",
"status": 401
Expand Down
4 changes: 2 additions & 2 deletions src/pages/documentation/api-docs/error-management.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Because of this, all error responses will have `Content-Type: application/proble

```json
{
"type": "TAG_NOT_FOUND",
"type": "https://shlink.io/api/error/tag-not-found",
"detail": "Tag with name \"foo\" could not be found",
"title": "Tag not found",
"status": 404
Expand All @@ -29,7 +29,7 @@ Some errors can have extra properties, depending on their nature.

```json
{
"type": "INVALID_SLUG",
"type": "https://shlink.io/api/error/non-unique-slug",
"detail": "Provided slug \"custom\" is already in use",
"title": "Invalid custom slug",
"status": 400,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Available commands:
list List commands
api-key
api-key:disable Disables an API key.
api-key:generate Generates a new valid API key.
api-key:generate Generate a new valid API key.
api-key:list Lists all the available API keys.
domain
domain:list List all domains that have been ever used for some short URL
Expand All @@ -53,6 +53,7 @@ Available commands:
short-url:delete Deletes a short URL
short-url:import Allows to import short URLs from third party sources
short-url:list List all short URLs
short-url:manage-rules Set redirect rules for a short URL
short-url:parse Returns the long URL behind a short code
short-url:visits Returns the detailed visits information for provided short code
short-url:visits-delete Deletes visits from a short URL
Expand Down
Loading

0 comments on commit 66c8026

Please sign in to comment.