diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2198b31 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,3 @@ +FROM mcr.microsoft.com/dotnet/core/sdk:3.0 AS builder + +COPY . . \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..bcffd4a --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,37 @@ +pipeline +{ + agent { dockerfile true } + stages + { + stage('Build') + { + steps + { + sh 'dotnet restore' + sh 'dotnet build -c Release --no-restore' + sh "dotnet pack -c Release --no-restore --no-build -p:PackageVersion=1.0.${env.BUILD_NUMBER}" + } + } + + stage('Archive artifacts') + { + steps + { + archiveArtifacts artifacts:'*/bin/Release/**/*.nupkg' + } + } + + stage('Publish nuget artifacts') + { + steps + { + sh "./JenkinsfileScripts/PublishNugetArtifacts.sh ${env.BUILD_NUMBER}" + } + } + } + post { + always { + deleteDir() /* clean up our workspace */ + } + } +} \ No newline at end of file diff --git a/JenkinsfileScripts/PublishNugetArtifacts.sh b/JenkinsfileScripts/PublishNugetArtifacts.sh new file mode 100644 index 0000000..6cb700b --- /dev/null +++ b/JenkinsfileScripts/PublishNugetArtifacts.sh @@ -0,0 +1,5 @@ +echo "Publish version $1 artifacts" +BUILD_NUMBER = $1 +for f in */bin/Release/**/*.nupkg; do + dotnet nuget push "$f" -k "8bb434b9-48cb-345d-a089-5216967caac1" -s "http://artifacts.successfulcode.com/repository/nuget-hosted/" +done \ No newline at end of file diff --git a/SuccessfulCodeTools.sln b/SuccessfulCodeTools.sln new file mode 100644 index 0000000..79fa6aa --- /dev/null +++ b/SuccessfulCodeTools.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29424.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SuccessfulCodeTools", "SuccessfulCodeTools\SuccessfulCodeTools.csproj", "{0CF60384-D7C3-491D-B3FD-73BDA7EE8B89}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0CF60384-D7C3-491D-B3FD-73BDA7EE8B89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CF60384-D7C3-491D-B3FD-73BDA7EE8B89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CF60384-D7C3-491D-B3FD-73BDA7EE8B89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CF60384-D7C3-491D-B3FD-73BDA7EE8B89}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {637B2883-A181-479C-A89E-41633912FDEF} + EndGlobalSection +EndGlobal diff --git a/SuccessfulCodeTools/Extensions/ObjectExtensions.cs b/SuccessfulCodeTools/Extensions/ObjectExtensions.cs new file mode 100644 index 0000000..1f4f89e --- /dev/null +++ b/SuccessfulCodeTools/Extensions/ObjectExtensions.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; +using System; + +namespace SuccessfulCodeTools.Extensions +{ + public static class ObjectExtensions + { + public static Lazy _settings = new Lazy(GetSettings); + + private static JsonSerializerSettings GetSettings() + { + var settings = new JsonSerializerSettings() + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + settings.Converters.Add(new StringEnumConverter()); + settings.NullValueHandling = NullValueHandling.Ignore; + return settings; + } + + public static string ToJson(this object obj) + { + return JsonConvert.SerializeObject(obj, _settings.Value); + } + } +} diff --git a/SuccessfulCodeTools/Extensions/StringExtensions.cs b/SuccessfulCodeTools/Extensions/StringExtensions.cs new file mode 100644 index 0000000..60d45d6 --- /dev/null +++ b/SuccessfulCodeTools/Extensions/StringExtensions.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json; +using System; + +namespace SuccessfulCodeTools +{ + public static class StringExtensions + { + public static T DeserializeJson(this string value) + { + return JsonConvert.DeserializeObject(value); + } + + public static T ToEnum(this string value) + { + return (T)Enum.Parse(typeof(T), value, true); + } + + public static int ToInt(this string value) + { + return Int32.Parse(value); + } + } +} diff --git a/SuccessfulCodeTools/LowLevelHttpClient/CustomHttpClient.cs b/SuccessfulCodeTools/LowLevelHttpClient/CustomHttpClient.cs new file mode 100644 index 0000000..af339bd --- /dev/null +++ b/SuccessfulCodeTools/LowLevelHttpClient/CustomHttpClient.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace SuccessfulCodeTools.LowLevelHttpClient +{ + public class CustomHttpClient + { + public int? OutgoingPort { get; set; } + public KeyValueCollection DefaultHeaders = new KeyValueCollection(); + public CustomHttpClient(int? outgoingPort = null) + { + OutgoingPort = outgoingPort; + } + + public async Task SendAsync(CustomHttpRequestMessage message, int? outgoingPort = null) + { + var defaultHeaders = DefaultHeaders.Where(e => !message.Headers.Contains(e)).ToArray(); + message.Headers.AddRange(defaultHeaders); + + var outBoundPort = outgoingPort ?? OutgoingPort ?? 0; + string result = string.Empty; + var ipLocalEndPoint = new IPEndPoint(IPAddress.Any, outBoundPort); + using (var tcp = new TcpClient(ipLocalEndPoint)) + { + var remoteEndpoint = new IPEndPoint(Dns.GetHostEntry(message.Uri.Host).AddressList[0], 80); + + tcp.Connect(remoteEndpoint); + tcp.LingerState.Enabled = false; + tcp.LingerState.LingerTime = 0; + tcp.SendTimeout = 500; + tcp.ReceiveTimeout = 1000; + // Send request headers + //builder.AppendLine("Content-Length: " + data.Length); // only for POST request + var request = message.ToString(); + var header = Encoding.ASCII.GetBytes(request); + var stream = tcp.GetStream(); + await stream.WriteAsync(header, 0, header.Length); + + // Send payload data if you are POST request + //await stream.WriteAsync(data, 0, data.Length); + + // receive data + using (var memory = new MemoryStream()) + { + await stream.CopyToAsync(memory); + tcp.Client.Disconnect(true); + return new CustomHttpResponseMessage(memory); + } + } + } + + } +} diff --git a/SuccessfulCodeTools/LowLevelHttpClient/CustomHttpRequestMessage.cs b/SuccessfulCodeTools/LowLevelHttpClient/CustomHttpRequestMessage.cs new file mode 100644 index 0000000..278735f --- /dev/null +++ b/SuccessfulCodeTools/LowLevelHttpClient/CustomHttpRequestMessage.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Net.Http; +using System.Text; + +namespace SuccessfulCodeTools.LowLevelHttpClient +{ + public class CustomHttpRequestMessage + { + public KeyValueCollection Headers = new KeyValueCollection(); + public Uri Uri { get; set; } + public string Method { get; set; } + + public CustomHttpRequestMessage(HttpMethod method, string url) + { + Uri = new Uri(url); + Method = method.ToString().ToUpper(); + } + + public override string ToString() + { + var builder = new StringBuilder(); + builder.AppendFormat("{0} {1} HTTP/1.1\r\n", Method.ToUpper(), Uri.PathAndQuery); + var hasHost = false; + var hasConnection = false; + foreach (var header in Headers) + { + if(header.Key.Equals("host", StringComparison.InvariantCultureIgnoreCase)) + { + hasHost = true; + } + if(header.Key.Equals("connection", StringComparison.InvariantCultureIgnoreCase)) + { + hasConnection = true; + } + builder.AppendFormat("{0}: {1}\r\n", header.Key, header.Value); + } + + if (!hasHost) + { + builder.AppendLine("Host: " + Uri.Host); + } + + if (!hasConnection) + { + builder.AppendLine("Connection: close"); + } + + builder.AppendLine(); + return builder.ToString(); + } + } +} diff --git a/SuccessfulCodeTools/LowLevelHttpClient/CustomHttpResponseMessage.cs b/SuccessfulCodeTools/LowLevelHttpClient/CustomHttpResponseMessage.cs new file mode 100644 index 0000000..7e869c0 --- /dev/null +++ b/SuccessfulCodeTools/LowLevelHttpClient/CustomHttpResponseMessage.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Text; + +namespace SuccessfulCodeTools.LowLevelHttpClient +{ + public class CustomHttpResponseMessage + { + public string Protocol { get; } + public HttpStatusCode StatusCode { get; } + public string StatusDescription { get; } + public KeyValuePair[] Headers { get; } + public string Content { get; } + public bool IsSuccessStatusCode { get; set; } + + public CustomHttpResponseMessage(MemoryStream memory) + { + memory.Position = 0; + var data = memory.ToArray(); + var index = BinaryMatch(data, Encoding.ASCII.GetBytes("\r\n\r\n")) + 4; + + var headers = Encoding.ASCII.GetString(data, 0, index); + var splitHeaders = headers.Split(new[] { "\r\n" }, StringSplitOptions.None); + var statusHeader = splitHeaders[0].Split(' '); + + Protocol = statusHeader[0]; + if(statusHeader[1][0] == '2') + { + IsSuccessStatusCode = true; + } + StatusCode = (HttpStatusCode)Int32.Parse(statusHeader[1]); + StatusDescription = statusHeader[2]; + + Headers = splitHeaders + .Skip(1) + .Where(e => !String.IsNullOrWhiteSpace(e)) + .Select(e => e.Split(new[] { ":" }, StringSplitOptions.None)) + .Select(e => new KeyValuePair(e[0], e[1].TrimStart())) + .ToArray(); + + + if (Headers.Any(e => e.Key.Equals("Transfer-Encoding", StringComparison.InvariantCultureIgnoreCase) && e.Value.Equals("chunked", StringComparison.InvariantCultureIgnoreCase))) + { + index = BinaryMatch(data, Encoding.ASCII.GetBytes("\r\n"), index) + 2; + } + + memory.Position = index; + if (!Headers.Where(e => e.Key == "Content-Encoding" && e.Value.StartsWith("gzip")).Any()) + { + Content = Encoding.UTF8.GetString(data, index, data.Length - index); + + return; + } + + using (var decompressionStream = new GZipStream(memory, CompressionMode.Decompress)) + using (var decompressedMemory = new MemoryStream()) + { + decompressionStream.CopyTo(decompressedMemory); + decompressedMemory.Position = 0; + Content = Encoding.UTF8.GetString(decompressedMemory.ToArray()); + } + } + + private static int BinaryMatch(byte[] input, byte[] pattern, int startIndex = 0) + { + int sLen = input.Length - pattern.Length + 1; + for (int i = startIndex; i < sLen; ++i) + { + bool match = true; + for (int j = 0; j < pattern.Length; ++j) + { + if (input[i + j] != pattern[j]) + { + match = false; + break; + } + } + if (match) + { + return i; + } + } + return -1; + } + } +} \ No newline at end of file diff --git a/SuccessfulCodeTools/LowLevelHttpClient/KeyValueCollection.cs b/SuccessfulCodeTools/LowLevelHttpClient/KeyValueCollection.cs new file mode 100644 index 0000000..aa01868 --- /dev/null +++ b/SuccessfulCodeTools/LowLevelHttpClient/KeyValueCollection.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace SuccessfulCodeTools.LowLevelHttpClient +{ + public class KeyValueCollection: IEnumerable> + { + public List> Items = new List>(); + + public void Add(T1 key, T2 value) + { + Items.Add(new KeyValuePair(key, value)); + } + + public void Remove(T1 key, T2 value) + { + Items.Remove(new KeyValuePair(key, value)); + } + + public void Remove(T1 key) + { + Items.RemoveAll(e => e.Key.Equals(key)); + } + + public IEnumerator> GetEnumerator() + { + return Items.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return Items.GetEnumerator(); + } + + internal void AddRange(IEnumerable> defaultHeaders) + { + Items.AddRange(defaultHeaders); + } + } +} \ No newline at end of file diff --git a/SuccessfulCodeTools/SuccessfulCodeTools.csproj b/SuccessfulCodeTools/SuccessfulCodeTools.csproj new file mode 100644 index 0000000..6e86919 --- /dev/null +++ b/SuccessfulCodeTools/SuccessfulCodeTools.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + +