Skip to content

Instantly share code, notes, and snippets.

@BobPusateri
Last active March 5, 2023 18:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BobPusateri/84b03f9c8877fdefd546edd29ae3a8a8 to your computer and use it in GitHub Desktop.
Save BobPusateri/84b03f9c8877fdefd546edd29ae3a8a8 to your computer and use it in GitHub Desktop.
Sample code for redirection service using Azure Functions & Cosmos DB
using System.Net;
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Configuration;
namespace URLRedirect
{
public class Redirect
{
private static Lazy<CosmosClient> _lazyClient = new Lazy<CosmosClient>(InitializeCosmosClient);
private CosmosClient _cosmosClient => _lazyClient.Value;
private static CosmosClient InitializeCosmosClient() {
// initialize configuration object from Function App environment variables
IConfigurationRoot config = new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
return new CosmosClient(
// get endpoint and token from configuration object
accountEndpoint: config["COSMOS_ENDPOINT"],
authKeyOrResourceToken: config["COSMOS_KEY"]
);
}
// Define Azure Function "DoRedirect" as an anonymous GET function with one parameter, "redirectKey"
[Function("DoRedirect")]
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route ="{redirectKey}")] HttpRequestData req,
string redirectKey)
{
// default target URL. If a result can't be found or an error otherwise occurs, redirect to here.
string targetUrl = "https://www.bobpusateri.com";
ItemResponse<RedirectObj> itemResult = null;
// Cosmos DB lookups are case-sensitive, so do a lowercase comparison.
string redirectKeyLower = redirectKey.ToLower();
PartitionKey partitionKey = new PartitionKey(redirectKeyLower);
bool recordHit = false;
Container container = _cosmosClient.GetContainer("<db_name>", "<container-name>");
// query Cosmos DB for a record matching the redirection key
Task<ItemResponse<RedirectObj>> ItemReadTask = container.ReadItemAsync<RedirectObj>(redirectKeyLower, partitionKey);
// create response object & add some caching settings to it
HttpResponseData response = req.CreateResponse(HttpStatusCode.Found);
response.Headers.Add("cache-control", "max-age=0, public, stale-if-error=86400");
try{
itemResult = await ItemReadTask;
RedirectObj i = itemResult.Resource;
// if a match is found and it's enabled, save the target URL and note a hit occurred
if(i.enabled) {
targetUrl = i.targetUrl;
recordHit = true;
}
} catch {
// I'm just absorbing errors at this time - planning to add logging later
}
// update hit data
try {
// if a hit occurred, update the hit counter and date for this record
if(recordHit) {
List<PatchOperation> updateOperations = new() {
// increment use count by 1, create if it doesn't exist
PatchOperation.Increment("/use_count", 1)
};
if(itemResult!.Resource.last_used_utc < DateTime.UtcNow) {
// add/update last used date (date only) if it's greater than the last entry
updateOperations.Add(PatchOperation.Add("/last_used_utc", DateTime.UtcNow.Date));
}
// update the document
_ = container.PatchItemAsync<RedirectObj>(
id: redirectKeyLower,
partitionKey: partitionKey,
patchOperations: updateOperations
);
}
} catch {
// intentionally blank
}
// add redirect header to response object & return to client
response.Headers.Add("location", targetUrl);
return response;
} // Run()
} // class Redirect
} // EOF
namespace URLRedirect {
// object used for serializing to/from Cosmos DB
public class RedirectObj {
// Lookup ID for the redirect. This is the value shown in the URL
public string id {get; set;}
// is this redirect enabled?
// if not, function will pretend it doesn't exist at all
public bool enabled {get; set;}
// url to redirect users to
public string targetUrl {get; set;}
// number of times the redirect has been used
public int use_count {get; set;}
// last time the redirect has been used
public DateTime last_used_utc {get; set;}
}
}
// example JSON document for a redirect entry in Cosmos DB
{
"id": "zombocom",
"targetUrl": "https://zombo.com/",
"enabled": 1
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment