diff --git a/.gitignore b/.gitignore index 894a44cc066..6c3c2bc51f5 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,349 @@ venv.bak/ # mypy .mypy_cache/ + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ +# ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true +**/wwwroot/lib/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- Backup*.rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ \ No newline at end of file diff --git a/README.md b/README.md index 1e20d1fdfa5..231bbb3c144 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ This repository contains useful tools that the Azure SDK team utilizes across th | Package or Intent | Path | Description | Status | |-------------------|-----------------------------------------|-----------------------------------------------------------------|----------| | doc-warden | [Readme](packages/python-packages/doc-warden/README.md) | A tool used to enforce readme standards across Azure SDK Repos. |[![Build Status](https://dev.azure.com/azure-sdk/public/_apis/build/status/108?branchName=master)](https://dev.azure.com/azure-sdk/public/_build/latest?definitionId=108&branchName=master) | +| pixel-server | [Readme](packages/web/pixel-server/README.md) | A tiny ASP.NET Core site used to serve a pixel and record impressions. | Not Yet Enabled | # Contributing diff --git a/web/pixel-server/PixelServer.sln b/web/pixel-server/PixelServer.sln new file mode 100644 index 00000000000..5c064d6887c --- /dev/null +++ b/web/pixel-server/PixelServer.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.106 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixelServer", "PixelServer\PixelServer.csproj", "{65040EF3-BB67-48A7-8357-908D84075F78}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {65040EF3-BB67-48A7-8357-908D84075F78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65040EF3-BB67-48A7-8357-908D84075F78}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65040EF3-BB67-48A7-8357-908D84075F78}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65040EF3-BB67-48A7-8357-908D84075F78}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {97DEB234-007A-48F6-AFB4-9542BE0EF08C} + EndGlobalSection +EndGlobal diff --git a/web/pixel-server/PixelServer/Connected Services/Application Insights/ConnectedService.json b/web/pixel-server/PixelServer/Connected Services/Application Insights/ConnectedService.json new file mode 100644 index 00000000000..94232b3815c --- /dev/null +++ b/web/pixel-server/PixelServer/Connected Services/Application Insights/ConnectedService.json @@ -0,0 +1,7 @@ +{ + "ProviderId": "Microsoft.ApplicationInsights.ConnectedService.ConnectedServiceProvider", + "Version": "8.14.11009.1", + "GettingStartedDocument": { + "Uri": "https://go.microsoft.com/fwlink/?LinkID=798432" + } +} \ No newline at end of file diff --git a/web/pixel-server/PixelServer/Controllers/TrackingController.cs b/web/pixel-server/PixelServer/Controllers/TrackingController.cs new file mode 100644 index 00000000000..30946eaec47 --- /dev/null +++ b/web/pixel-server/PixelServer/Controllers/TrackingController.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.ApplicationInsights; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Caching.Memory; + +namespace PixelServer.Controllers +{ + [Route("api/impressions")] + [ApiController] + public class TrackingController : ControllerBase + { + private readonly IMemoryCache _cache; + private readonly TelemetryClient _telemetry; + + public TrackingController(IMemoryCache memoryCache, TelemetryClient telemetry) + { + _cache = memoryCache; + _telemetry = telemetry; + } + + /// + /// Currently the only entrypoint, services a 1 pixel image while recording that there was an impression for the specific path. + /// + /// Uses in-memory cache of afore-seen IPs to recognize a "duplicate" impression. The addresses themselves are not recorded. + /// + /// + /// + [HttpGet] + public IActionResult Get(string path) + { + _telemetry.TrackEvent("PixelImpression", new Dictionary() + { + { "visitor_duplicate", getCachedVisitorStatus(getRequestAddress()) }, + { "visitor_path", path } + }); + + return new ImageActionResult(); + } + + private string getCachedVisitorStatus(System.Net.IPAddress address) + { + if (!_cache.TryGetValue(address, out _)) + { + // Set cache options. + var cacheEntryOptions = new MemoryCacheEntryOptions() + // Keep in cache for this time, reset time if accessed. + .SetSlidingExpiration(TimeSpan.FromHours(1)); + + // Save data in cache. + _cache.Set(address, DateTime.UtcNow, cacheEntryOptions); + + return "false"; + } + + return "true"; + } + + private System.Net.IPAddress getRequestAddress() + { + return Request.HttpContext.Connection.RemoteIpAddress; + } + + private class ImageActionResult : IActionResult + { + private static readonly byte[] _imgPayload = System.IO.File.ReadAllBytes(AppDomain.CurrentDomain.BaseDirectory + "/Etc/pixel.png"); + + public Task ExecuteResultAsync(ActionContext context) + { + context.HttpContext.Response.StatusCode = StatusCodes.Status200OK; + context.HttpContext.Response.ContentType = "image/png"; + context.HttpContext.Response.ContentLength = _imgPayload.Length; + return context.HttpContext.Response.Body.WriteAsync(_imgPayload, 0, _imgPayload.Length); + } + } + + } +} diff --git a/web/pixel-server/PixelServer/Etc/pixel.png b/web/pixel-server/PixelServer/Etc/pixel.png new file mode 100644 index 00000000000..818c71d03f4 Binary files /dev/null and b/web/pixel-server/PixelServer/Etc/pixel.png differ diff --git a/web/pixel-server/PixelServer/PixelServer.csproj b/web/pixel-server/PixelServer/PixelServer.csproj new file mode 100644 index 00000000000..6d51201f4a7 --- /dev/null +++ b/web/pixel-server/PixelServer/PixelServer.csproj @@ -0,0 +1,30 @@ + + + + netcoreapp2.1 + /subscriptions/a18897a6-7e44-457d-9260-f2854c0aca42/resourcegroups/sdk-bi-data/providers/microsoft.insights/components/azure-sdk-bi + /subscriptions/a18897a6-7e44-457d-9260-f2854c0aca42/resourcegroups/sdk-bi-data/providers/microsoft.insights/components/azure-sdk-bi + 317bfab0-0fc1-4048-b61e-67e8f96c2b3a + + + + + + + + + + + + + + + + + + + Always + + + + diff --git a/web/pixel-server/PixelServer/Program.cs b/web/pixel-server/PixelServer/Program.cs new file mode 100644 index 00000000000..c9cd1c52136 --- /dev/null +++ b/web/pixel-server/PixelServer/Program.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace PixelServer +{ + public class Program + { + public static void Main(string[] args) + { + var webHostBuilder = new WebHostBuilder() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup() + .UseDefaultServiceProvider( + (context, options) => options.ValidateScopes = context.HostingEnvironment.IsDevelopment()) + .UseKestrel(); + + webHostBuilder.UseSockets(x => x.IOQueueCount = 2); + webHostBuilder.UseApplicationInsights(); + webHostBuilder.Build().Run(); + } + } +} diff --git a/web/pixel-server/PixelServer/Properties/launchSettings.json b/web/pixel-server/PixelServer/Properties/launchSettings.json new file mode 100644 index 00000000000..ff695889bcd --- /dev/null +++ b/web/pixel-server/PixelServer/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:64742", + "sslPort": 44395 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/impressions", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "PixelServer": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "api/impressions", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/web/pixel-server/PixelServer/Startup.cs b/web/pixel-server/PixelServer/Startup.cs new file mode 100644 index 00000000000..bcd1ba4bc6d --- /dev/null +++ b/web/pixel-server/PixelServer/Startup.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace PixelServer +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMemoryCache(); + services.AddApplicationInsightsTelemetry(Configuration); + services.AddMvcCore(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseMvc(); + } + } +} diff --git a/web/pixel-server/PixelServer/appsettings.Development.json b/web/pixel-server/PixelServer/appsettings.Development.json new file mode 100644 index 00000000000..e203e9407e7 --- /dev/null +++ b/web/pixel-server/PixelServer/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/web/pixel-server/PixelServer/appsettings.json b/web/pixel-server/PixelServer/appsettings.json new file mode 100644 index 00000000000..95ab1485719 --- /dev/null +++ b/web/pixel-server/PixelServer/appsettings.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*", + "ApplicationInsights": { + "InstrumentationKey": "SetInAzure" + } +} diff --git a/web/pixel-server/README.md b/web/pixel-server/README.md new file mode 100644 index 00000000000..feea12bf57e --- /dev/null +++ b/web/pixel-server/README.md @@ -0,0 +1,52 @@ +# Pixel Server + +This site is intended as a backing server to serve pixels being loaded on a web page. Note that the limit of data retrieved here is merely: + +> a pixel with query string `x` was requested + +No PII data is recorded. + +## Prerequisites +* This project was built, tested, and peformance checked using `.NET Core 2.1`. +* It is intended (and has been tested) for deployment to an `Azure AppService` +* The backing data store is `Application Insights`, so you will need to create one prior to deployment. + +## Usage +To add a tracking pixel, simply embed an image of the following form. + +``` +Markdown: +![AltText](https://.azurewebsites.net/api/impressions?path= "AltText") + +RST: +.. image:: https://.azurewebsites.net/api/impressions?path= + +Img URL: + +``` + +## Deployment and Setup + +This site is currently configured to leverage `Application Insights` as a connected service. With the code that is currently checked in, the order to deploy your own version of this site is as follows: + +* Deploy site in `release` configuration to your AppService +* Enable Application Insights on your AppService + +## Enabling Application Insights on your AppService + +First, install the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest). Use the `az login` command to sign in with the appropriate email to access your azure subscriptions. After that, ensure you've created an `Application Insights` where it is visible to your target AppService. + +Then, leverage the following az commands: + +``` +#retrieve the InstrumentationKey for your previously created appinsights +$key = az resource show -g -n --resource-type "Microsoft.Insights/components" --query properties.InstrumentationKey + +#set the application insight for web app +az webapp config appsettings set -g -n --settings "APPINSIGHTS_INSTRUMENTATIONKEY = $key" +``` + +Or, if you the above is too much trouble. Go through the UI. + +![Where To Click](example.png) + diff --git a/web/pixel-server/example.png b/web/pixel-server/example.png new file mode 100644 index 00000000000..3b2360ee1c8 Binary files /dev/null and b/web/pixel-server/example.png differ