Skip to content

Commit

Permalink
feature/cron-expressions (#93)
Browse files Browse the repository at this point in the history
- Added cron.jl module for parsing cron expressions
- Added support for week and month abbreviations and single values in cron expressions
- Fixed bugs related to single-value expressions and added support for partial patterns and special patterns in cron expressions
- Integrated cron module with router function and added cron job example
- Added logging to repeat tasks and cron jobs, and improved logging formats
- Refactored variable names, fixed bugs in cron scheduling logic, and added default mimetype value for missing values
- Added exports for cron helpers, updated cron tests, and made serve functions return server object if run in async mode
- Added more cron test cases and documentation for cron scheduling feature
- Bumped version to v1.1.6
  • Loading branch information
ndortega authored Apr 6, 2023
1 parent 85480bc commit fa69326
Show file tree
Hide file tree
Showing 14 changed files with 1,403 additions and 29 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Oxygen"
uuid = "df9a0d86-3283-4920-82dc-4555fc0d1d8b"
authors = ["Nathan Ortega <[email protected]>"]
version = "1.1.5"
version = "1.1.6"

[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Expand Down
89 changes: 84 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ Breathe easy knowing you can quickly spin up a web server with abstractions you'
- Out-of-the-box JSON serialization & deserialization (customizable)
- Type definition support for path parameters
- Built-in multithreading support
- Static file hosting
- Built-in Cron Scheduling (on endpoints & functions)
- Middleware chaining (at the application, router, and route levels)
- Static & Dynamic file hosting
- Route tagging
- Repeat tasks

Expand Down Expand Up @@ -204,11 +205,12 @@ The `router()` function is an HOF (higher order function) that allows you to reu

Below are the arguments the `router()` function can take:
```julia
router(prefix::String; tags::Vector, middleware::Vector, interval::Real)
router(prefix::String; tags::Vector, middleware::Vector, interval::Real, cron::String)
```
- `tags` are used to organize endpoints in the autogenerated docs
- `middleware` is used to setup router & route-specific middleware
- `interval` is used to support repeat actions (*calling a request handler on a set interval in seconds*)
- `tags` - are used to organize endpoints in the autogenerated docs
- `middleware` - is used to setup router & route-specific middleware
- `interval` - is used to support repeat actions (*calling a request handler on a set interval in seconds*)
- `cron` - is used to specify a cron expression that determines when to call the request handler.

```julia
using Oxygen
Expand All @@ -229,6 +231,83 @@ end
serve()
```

## Cron Scheduling

Oxygen comes with a built-in cron scheduling system that allows you to call endpoints and functions automatically when the cron expression matches the current time.

When a job is scheduled, a new task is created and runs in the background. Each task uses its given cron expression and the current time to determine how long it needs to sleep before it can execute.

The cron parser in Oxygen is based on the same specifications as the one used in Spring. You can find more information about this on the [Spring Cron Expressions](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/support/CronExpression.html) page.

### Cron Expression Syntax
The following is a breakdown of what each parameter in our cron expression represents. While our specification closely resembles the one defined by Spring, it's not an exact 1-to-1 match.
```
The string has six single space-separated time and date fields:
┌───────────── second (0-59)
│ ┌───────────── minute (0 - 59)
│ │ ┌───────────── hour (0 - 23)
│ │ │ ┌───────────── day of the month (1 - 31)
│ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)
│ │ │ │ │ ┌───────────── day of the week (1 - 7)
│ │ │ │ │ │ (Monday is 1, Tue is 2... and Sunday is 7)
│ │ │ │ │ │
* * * * * *
```
Partial expressions are also supported, which means that subsequent expressions can be left out (they are defaulted to `'*'`).

```julia
# In this example we see only the `seconds` part of the expression is defined.
# This means that all following expressions are automatically defaulted to '*' expressions
@cron "*/2" function()
println("runs every 2 seconds")
end
```

### Scheduling Endpoints

The `router()` function has a keyword argument called `cron`, which accepts a cron expression that determines when an endpoint is called. Just like the other keyword arguments, it can be reused by endpoints that share routers or be overridden by inherited endpoints.

```julia
# execute at 8, 9 and 10 o'clock of every day.
@get router("/cron-example", cron="0 0 8-10 * * *") function(req)
println("here")
end

# execute this endpoint every 5 seconds (whenever current_seconds % 5 == 0)
every5 = router("/cron", cron="*/5")

# this endpoint inherits the cron expression
@get every5("/first") function(req)
println("first")
end

# Now this endpoint executes every 2 seconds ( whenever current_seconds % 2 == 0 ) instead of every 5
@get every5("/second", cron="*/2") function(req)
println("second")
end
```

### Scheduling Functions

In addition to scheduling endpoints, you can also use the new `@cron` macro to schedule functions. This is useful if you want to run code at specific times without making it visible or callable in the API.

```julia
@cron "*/2" function()
println("runs every 2 seconds")
end

@cron "0 0/30 8-10 * * *" function()
println("runs at 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day")
end
```

### Starting & Stopping Cron Jobs

When you run `serve()` or `serveparallel()`, all registered cron jobs are automatically started. If the server is stopped or killed, all running jobs will also be terminated. You can stop the server and all repeat tasks and cron jobs by calling the `terminate()` function or manually killing the server with `ctrl+C`.

In addition, Oxygen provides utility functions to manually start and stop cron jobs: `startcronjobs()` and `stopcronjobs()`. These functions can be used outside of a web server as well.

## Repeat Tasks

The `router()` function has an `interval` parameter which is used to call
Expand Down
58 changes: 58 additions & 0 deletions demo/crondemo.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
module CronDemo

include("../src/Oxygen.jl")
using .Oxygen
using HTTP
using Dates

# You can use the @cron macro directly

@cron "*/2" function()
println("every 2 seconds")
end

@cron "*/5" function every5seconds()
println("every 5 seconds")
end

value = 0

# You can also just use the 'cron' keyword that's apart of the router() function
@get router("/increment", cron="*/11", interval=4) function()
global value += 1
return value
end

@get router("/getvalue") function()
return value
end

# all endpoints will inherit this cron expression
pingpong = router("/pingpong", cron="*/3")

@get pingpong("/ping") function()
println("ping")
return "ping"
end

# here we override the inherited cron expression
@get pingpong("/pong", cron="*/7") function()
println("pong")
return "pong"
end

@get "/home" function()
"home"
end

@get "/stop" function()
stopcronjobs()
end

@get "/start" function()
startcronjobs()
end

serve()

end
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ makedocs(
"tutorial/path_parameters.md",
"tutorial/query_parameters.md",
"tutorial/request_body.md",
"tutorial/cron_scheduling.md",
"tutorial/bigger_applications.md",
"tutorial/oauth2.md"
]
Expand Down
11 changes: 9 additions & 2 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,20 @@ json
binary
```

## Repeat Tasks & Cron Scheduling
```@docs
@cron
starttasks
stoptasks
startcronjobs
stopcronjobs
```

## Extra's
```@docs
router
internalrequest
redirect
terminate
starttasks
stoptasks
resetstate
```
89 changes: 84 additions & 5 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ Breathe easy knowing you can quickly spin up a web server with abstractions you'
- Out-of-the-box JSON serialization & deserialization (customizable)
- Type definition support for path parameters
- Built-in multithreading support
- Static file hosting
- Built-in Cron Scheduling (on endpoints & functions)
- Middleware chaining (at the application, router, and route levels)
- Static & Dynamic file hosting
- Route tagging
- Repeat tasks

Expand Down Expand Up @@ -204,11 +205,12 @@ The `router()` function is an HOF (higher order function) that allows you to reu

Below are the arguments the `router()` function can take:
```julia
router(prefix::String; tags::Vector, middleware::Vector, interval::Real)
router(prefix::String; tags::Vector, middleware::Vector, interval::Real, cron::String)
```
- `tags` are used to organize endpoints in the autogenerated docs
- `middleware` is used to setup router & route-specific middleware
- `interval` is used to support repeat actions (*calling a request handler on a set interval in seconds*)
- `tags` - are used to organize endpoints in the autogenerated docs
- `middleware` - is used to setup router & route-specific middleware
- `interval` - is used to support repeat actions (*calling a request handler on a set interval in seconds*)
- `cron` - is used to specify a cron expression that determines when to call the request handler.

```julia
using Oxygen
Expand All @@ -229,6 +231,83 @@ end
serve()
```

## Cron Scheduling

Oxygen comes with a built-in cron scheduling system that allows you to call endpoints and functions automatically when the cron expression matches the current time.

When a job is scheduled, a new task is created and runs in the background. Each task uses its given cron expression and the current time to determine how long it needs to sleep before it can execute.

The cron parser in Oxygen is based on the same specifications as the one used in Spring. You can find more information about this on the [Spring Cron Expressions](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/support/CronExpression.html) page.

### Cron Expression Syntax
The following is a breakdown of what each parameter in our cron expression represents. While our specification closely resembles the one defined by Spring, it's not an exact 1-to-1 match.
```
The string has six single space-separated time and date fields:
┌───────────── second (0-59)
│ ┌───────────── minute (0 - 59)
│ │ ┌───────────── hour (0 - 23)
│ │ │ ┌───────────── day of the month (1 - 31)
│ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)
│ │ │ │ │ ┌───────────── day of the week (1 - 7)
│ │ │ │ │ │ (Monday is 1, Tue is 2... and Sunday is 7)
│ │ │ │ │ │
* * * * * *
```
Partial expressions are also supported, which means that subsequent expressions can be left out (they are defaulted to `'*'`).

```julia
# In this example we see only the `seconds` part of the expression is defined.
# This means that all following expressions are automatically defaulted to '*' expressions
@cron "*/2" function()
println("runs every 2 seconds")
end
```

### Scheduling Endpoints

The `router()` function has a keyword argument called `cron`, which accepts a cron expression that determines when an endpoint is called. Just like the other keyword arguments, it can be reused by endpoints that share routers or be overridden by inherited endpoints.

```julia
# execute at 8, 9 and 10 o'clock of every day.
@get router("/cron-example", cron="0 0 8-10 * * *") function(req)
println("here")
end

# execute this endpoint every 5 seconds (whenever current_seconds % 5 == 0)
every5 = router("/cron", cron="*/5")

# this endpoint inherits the cron expression
@get every5("/first") function(req)
println("first")
end

# Now this endpoint executes every 2 seconds ( whenever current_seconds % 2 == 0 ) instead of every 5
@get every5("/second", cron="*/2") function(req)
println("second")
end
```

### Scheduling Functions

In addition to scheduling endpoints, you can also use the new `@cron` macro to schedule functions. This is useful if you want to run code at specific times without making it visible or callable in the API.

```julia
@cron "*/2" function()
println("runs every 2 seconds")
end

@cron "0 0/30 8-10 * * *" function()
println("runs at 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day")
end
```

### Starting & Stopping Cron Jobs

When you run `serve()` or `serveparallel()`, all registered cron jobs are automatically started. If the server is stopped or killed, all running jobs will also be terminated. You can stop the server and all repeat tasks and cron jobs by calling the `terminate()` function or manually killing the server with `ctrl+C`.

In addition, Oxygen provides utility functions to manually start and stop cron jobs: `startcronjobs()` and `stopcronjobs()`. These functions can be used outside of a web server as well.

## Repeat Tasks

The `router()` function has an `interval` parameter which is used to call
Expand Down
Loading

2 comments on commit fa69326

@ndortega
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/81186

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v1.1.6 -m "<description of version>" fa693264cbaea63c79b9ba252d1a6d093472bd4b
git push origin v1.1.6

Please sign in to comment.