Skip to content

Data science focused web framework

License

Notifications You must be signed in to change notification settings

DanceJL/Dance.jl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

94 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Dance

Build Status
Build Status codecov

1 - Introduction

Julia is an excellent backend language (read more), powering numerous Artificial Intelligence and Big Data applications. However, integrating these results into web output is not the job of a data scientist, nor should it be complicated.

That said, the aim of Dance is to facilitate process by allowing output/reception of:

  • Dict {Symbol, Any}
  • DataFrame

to/from:

  • JSON API
  • JavaScript string in HTML page

simply by adding rendering function as a parameter, when building route list.

That way you can take advantage of powerful frontend JavaScript frameworks, through easy collaboration with frontend developers.

Dance can be used as starting base of new project, as well as web layer addition to existing project.


2 - Installation

Package can be installed with Julia's package manager, either by pressing ] to get the Pkg REPL mode and doing:

pkg> add Dance

or by using Pkg functions:

julia> using Pkg; Pkg.add("Dance")

Compatibility is with Julia 1.1 upward.

3 - Setup

Invoke terminal in working directory and:

using Dance
start_project("project name")

This will create a new directory specified by project name parameter and copy necessary files over.

Files include:

  • dance.jl: main entry point of Dance to be always called from terminal
  • routes.jl: main routes list file
  • settings/Global.jl: main project settings
  • html/base.html: for HTML outputs this is default template
  • html/favicon.ico: favicon for HTML pages

Depending on environment, other files can be included under settings directory to overwrite those under Global.jl:

  • One can add other parameters under these settings files, that will be accessible in project by reading from Main.Settings dict. ENV has been avoided due to potential leakage security issues.
  • Is recommended to use secrets.jl file included under Global.jl that will not be stored in version control, for sensitive authentication data.

Can be overwritten/moved:

  • routes.jl: move/rename and update Settings[:routes_filename] accordingly
  • html/base.html: move/rename and update Settings[:html_base_filename] accordingly
  • html/favicon.ico: move/rename and update Settings[:html_favicon_name] accordingly

=> Is recommended to overwrite api_access_control_allow_origin settings parameter, to limit API access to known hosts.

4 - Routes

4.1 - General

Routes can be included in main routes file (routes.jl by default), as follows:

route(path::Union{Regex, String}, action::Function; method::String=POST, endpoint=EP_JSON, html_file::String=Configuration.Settings[:html_base_filename]*".html")
  • Just pathand function are mandatory, kwargs can overwrite default values as necessary.

That said, note that:

  • path can either be fixed string or contain PCRE regex containing parameter names.
  • Adding an ending slash (/) tp path is optional, as incoming requests will have pending slash stripped.

Please see:

for all cases.

4.2 - Groups For Common Parameter Routes

If some routes share same path prefix or if you want to avoid repeating kwarg parameters, routes can be grouped into route groups as follows:

route_group(route_prefix="/dict", method=GET, endpoint=EP_HTML, [
    (path=r"/(?<value>\d.)", action=dict_1)
    (path=r"/(?<key>\w+)/(?<value>\d{3})", action=dict_2, html_file="html/file")
])

After specifying the common kwargs for routes in question, routes are passed as array of named tuples.

As for common kwargs, only set named tuple keys that are necessary to overwrite.

4.3 - Static Files

Dance can also serve static files.

Recommended method is to specify a static directory whose structure will be parsed when building routes for each of the directory's contents.

For example all contents of files directory will be accessible under static path:

static_dir("/static", "files")

If you have some other files that you would like to add individually, one can do so by passing path parameter as relative to project's root directory.

For instance if you have image.jpg in files relative to project root:

route("/files/image.jpg", output_file_as_string; method=GET, endpoint=EP_STATIC)

5 - Launching

Calling:

julia dance.jl

will start Dance as web server. Press ctrl + C to stop.

By calling:

julia dance.jl repl

one can enter the REPL mode after project environment has been loaded. Press ctrl + d to exit.

6 - Module Loading & Custom Startup Script

Note that when launching, Dance will add the current dir, as well as all sub-directories as module import path.

By static_dir as defined in routes.jl will be ignored during the procedure, as described under STEP 1 of dance.jl file. Should you require ignoring other directories for startup performance optimisation, please populate ignore_dirs under STEP 1.

As outlined under STEP 3 of dance.jl file, any custom scripts can be added, that will be run before Dance launches server/REPL.

7 - Security

7.1 - Incoming HTTP Headers

Never blindly trust an incoming HTTP request!

As all route functions receive incoming HTTP Headers, it is recommend when necessary to at least verify the Host and X-Forwarded-Host values. This is not included by default, in order to optimise performance, but here below a sample code, should you like to implement this extra security.

function valid_host() :: Bool
    allowed_hosts::Array{String, 1} = [Main.Settings[:server_host]]
    valid_forwarded_host::Bool = false
    valid_host::Bool = false

    if "127.0.0.1" in allowed_hosts
        push!(allowed_hosts, "localhost")
    end

    if !(Main.Settings[:server_port] in [80, 443])
        for (idx, item) in enumerate(allowed_hosts)
            allowed_hosts[idx] = item * ":" * string(Main.Settings[:server_port])
        end
    end

    for pair in request_headers
        if pair.first=="Host"
           valid_host = pair.second in allowed_hosts
        end
        if pair.first=="X-Forwarded-Host"
           valid_forwarded_host = pair.second in allowed_hosts
        end
    end

    return valid_host && valid_forwarded_host
end

8 - Running Dance Under Multi-processing Environment

Dance can be run in multi-process environment via Julia Distributed package. This is also particularly useful should you be planning on using cluster of machines in order to implement load balancer.

That said please only use this feature should your website expect heavy traffic or output functions be resource intensive, as else performance will degrade as spawning and data transfer between processes are expensive operations.

To do so edit the upper part of dance.jl as indicated in the file.