Last active
February 24, 2020 03:19
-
-
Save hemikak/8f9fcde07bcb23b2d15a3e1a240fd3f5 to your computer and use it in GitHub Desktop.
A simple web server written in Ballerina
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. | |
// | |
// WSO2 Inc. licenses this file to you under the Apache License, | |
// Version 2.0 (the "License"); you may not use this file except | |
// in compliance with the License. | |
// You may obtain a copy of the License at | |
// | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// | |
// Unless required by applicable law or agreed to in writing, | |
// software distributed under the License is distributed on an | |
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
// KIND, either express or implied. See the License for the | |
// specific language governing permissions and limitations | |
// under the License. | |
import ballerina/file; | |
import ballerina/filepath; | |
import ballerina/http; | |
import ballerina/log; | |
import ballerina/mime; | |
//// Configuration related properties. | |
boolean isSinglePageApplication = true; | |
// file extension mapping to content types | |
final map<string> MIME_MAP = { | |
"json": mime:APPLICATION_JSON, | |
"xml": mime:TEXT_XML, | |
balo: mime:APPLICATION_OCTET_STREAM, | |
css: "text/css", | |
gif: "image/gif", | |
html: mime:TEXT_HTML, | |
ico: "image/x-icon", | |
jpeg: "image/jpeg", | |
jpg: "image/jpeg", | |
js: "application/javascript", | |
png: "image/png", | |
svg: "image/svg+xml", | |
txt: mime:TEXT_PLAIN, | |
woff2: "font/woff2", | |
zip: "application/zip" | |
}; | |
@http:ServiceConfig { | |
basePath: "/", | |
chunking: http:CHUNKING_NEVER | |
} | |
service webServerSvc on new http:Listener(9090) { | |
@http:ResourceConfig { | |
methods: ["GET"], | |
path: "/*" | |
} | |
resource function serveHtmlFiles(http:Caller caller, http:Request request) { | |
http:Response res = new; | |
string requestedFilePath = <@untainted> <string> request.extraPathInfo; | |
requestedFilePath = checkpanic filepath:build("app", requestedFilePath); | |
// If requested for a file(not directory), check if its available in the server and response. | |
if (file:exists(requestedFilePath) && isFile(requestedFilePath)) { | |
res = getFileAsResponse(requestedFilePath); | |
error? clientResponse = caller->respond(res); | |
if (clientResponse is error) { | |
log:printError("unable respond back", err = clientResponse); | |
} | |
return; | |
} | |
// If requested is a directory, check and append forward slash. | |
if (!requestedFilePath.endsWith("/")) { | |
if (requestedFilePath.length() > 0) { | |
requestedFilePath = requestedFilePath + "/"; | |
} | |
} | |
// Resolve to index.html of the requested directory | |
requestedFilePath = requestedFilePath + "index.html"; // resolve to index.html if not file it found. | |
if (isSinglePageApplication && !file:exists(requestedFilePath)) { | |
res = getFileAsResponse(checkpanic filepath:build("app", "index.html")); | |
} else { | |
res = getFileAsResponse(requestedFilePath); | |
} | |
error? clientResponse = caller->respond(res); | |
if (clientResponse is error) { | |
log:printError("unable respond back", err = clientResponse); | |
} | |
} | |
@http:ResourceConfig { | |
methods: ["GET"], | |
path: "/static/*" | |
} | |
resource function serveStaticFiles(http:Caller caller, http:Request request) { | |
// TODO: properly untaint the request.rawPath | |
string|error requestedFilePath = filepath:build("app", <@untainted> <string> request.rawPath); | |
http:Response res = new; | |
if (requestedFilePath is string) { | |
res = getFileAsResponse(requestedFilePath); | |
} else { | |
res.setTextPayload("server error occurred."); | |
res.statusCode = 500; | |
log:printError("unable to resolve file path", err = requestedFilePath); | |
} | |
error? clientResponse = caller->respond(res); | |
if (clientResponse is error) { | |
log:printError("unable respond back", err = clientResponse); | |
} | |
} | |
} | |
# Serve a file as a http response. | |
# | |
# + requestedFile - The path of the file to server. | |
# + return - The http response. | |
function getFileAsResponse(string requestedFile) returns http:Response { | |
http:Response res = new; | |
// Figure out content-type | |
string contentType = mime:APPLICATION_OCTET_STREAM; | |
string fileExtension = checkpanic filepath:extension(requestedFile); | |
if (fileExtension != "") { | |
contentType = getMimeTypeByExtension(fileExtension); | |
} | |
// Check if file exists. | |
if (file:exists(requestedFile)) { | |
res.setFileAsPayload(requestedFile, contentType = contentType); | |
} else { | |
log:printError("unable to find file: " + requestedFile); | |
res.setTextPayload("the server was not able to find what you were looking for."); | |
res.statusCode = http:STATUS_NOT_FOUND; | |
} | |
return res; | |
} | |
# Get the content type using a file extension. | |
# | |
# + extension - The file extension. | |
# + return - The content type if a match is found, else application/octet-stream. | |
function getMimeTypeByExtension(string extension) returns string { | |
var contentType = MIME_MAP[extension.toLowerAscii()]; | |
if (contentType is string) { | |
return contentType; | |
} else { | |
return mime:APPLICATION_OCTET_STREAM; | |
} | |
} | |
# Check if a file path is a file and not a directory. | |
# | |
# + filePath - Path to the file | |
# + return - True if file, else false | |
function isFile(string filePath) returns boolean { | |
return !(file:readDir(filePath) is file:FileInfo[]); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment