2019-06-21

使用 SSH 連入 Windows 10 的 docker VM (MobyLinuxVM)

因為使用 docker volume create --name voluem_name 指令建立的內容會儲存在 docker vm 中,上網查到連入 docker vm 的連線方式:

docker run --privileged -it -v /var/run/docker.sock:/var/run/docker.sock jongallant/ubuntu-docker-client 
docker run --net=host --ipc=host --uts=host --pid=host -it --security-opt=seccomp=unconfined --privileged --rm -v /:/host alpine /bin/sh
chroot /host

參考:

2019-06-20

PostgreSQL 的資料庫名稱帶 \r 的刪除方式

在 postgresql 命令操作下查資料庫名稱

 Name             | 
------------------| 
DATABASE_NAME\r   | 

資料庫名後面帶有 \r,多出 \r 的原因是建立資料時透過 shell script,而這個 shell script 是在 Windows 環境的IDE編輯。所以覆製到 ubuntu 環境執行造成資料庫名稱多帶 \r

\r 的 byte 值是數值 13,13 轉 16進位(HEX 字串) 是 0D

要移除資料庫的指令是將資料庫名指定 unicode 格式,將 \r 轉為 unicode 格式的 \000D,完整指令如下:

drop database U&"DATABASE_NAME\000D"; 

或是更改資料庫名稱

alter database U&"DATABASE_NAME\000D" rename to "DATABASE_NAME_NEW"; 

2019-06-10

javascript 前端,使用 google-protobuf,後端用 asp.net core 2 接收

這篇是記錄在 Windows 10 環境中的 ASP.Net Core 的 WebAPI 使用 protobuf 取代 json 的資料傳輸

本文範例程式碼

系統中需已預安裝:

  • 安裝 .NET Core 2.2
  • 安裝 protoc.exe
  • 安裝 Browserify

protoc.exe 安裝

到 GitHub 的 Protocol Buffers Releases 下載 protoc-3.8.0-win64.zip

我是解壓到 C:\bin,這裡的 c:\bin 是已加到環境變數 PATH 中。

C:\bin
├───include
   └───google
       └───protobuf
└───protoc.exe

Browserify 安裝

npm install browserify -g

專案檔案結構

此文建立的範例專案名稱: ProtobufWebSample 省略部份檔案和目錄後結構如下:

~/ProtobufWebSample
├───ProtobufWeb
   ├───Controllers
      └───EchoController.cs
   └───ProtobufWeb.csproj
└───protos
    ├───EchoData.proto
    └───gen.bat

新增 asp.net core 的 WebAPI 專案

mkdir ProtobufWebSample
cd ProtobufWebSample
mkdir ProtobufWeb
cd ProtobufWeb
dotnet new webapi

安裝 Google.Protobuf 套件

ProtobufWebSample/ProtobufWeb 資料夾安裝 Google.Protobuf 套件

dotnet add package Google.Protobuf

建立 EchoData.proto

syntax = "proto3";

option csharp_namespace = "ProtobufWeb.ProtoGen";

message EchoData {
  string text = 1;
  int32 age = 2;
}

使用 protoc.exe 將 EchoData.proto 產生 C# 類別和 JavaScript 程式碼

產生 C# 程式碼

protoc.exe --proto_path=路徑\ProtobufWebSample\protos --js_out=路徑\ProtobufWebSample\protos\gen\csharp 路徑\ProtobufWebSample\protos\EchoData.proto

產生 JavaScript 程式碼

protoc.exe --proto_path=路徑\ProtobufWebSample\protos --csharp_out=路徑\ProtobufWebSample\protos\gen\js路徑\ProtobufWebSample\protos\EchoData.proto

因為要在瀏覽器中使用,所以要透過 browserify 將 google-protobuf.js 和產生出來的 EchoData_pb.js 綁在一起輸出為 bundle.js,瀏覽器使用時只要引用此 bundle.js即可。

ProtobufWebSample/protos/gen.bat

@echo on
SET PWD=%~dp0

set PROTOC=C:\bin\protoc.exe
set CSHARP_OUT=%PWD%gen\csharp
set JS_OUT=%PWD%gen\js

rd /S /Q %CSHARP_OUT%
md %CSHARP_OUT%
rd /S /Q %JS_OUT%
md %JS_OUT%

%PROTOC% --proto_path=C:/bin/include/google/protobuf --proto_path=%PWD%^
         --csharp_out=%CSHARP_OUT%^
         %PWD%EchoData.proto


%PROTOC% --proto_path=C:/bin/include/google/protobuf --proto_path=%PWD%^
         --js_out=import_style=commonjs,binary:%JS_OUT%^
         %PWD%EchoData.proto

echo var echodataProto = require('./EchoData_pb'); > %JS_OUT%\exports.js
echo module.exports = { >> %JS_OUT%\exports.js
echo     EchoDataProto: echodataProto >> %JS_OUT%\exports.js
echo } >> %JS_OUT%\exports.js

cd %JS_OUT%
call npm install google-protobuf

browserify exports.js > bundle.js

cd %PWD%

建立使用 EchoData 傳遞的 API EchoController

ProtobufWebSample/protos/gen/csharp/EchoData.cs 移動到Web專案中

mkdir ProtobufWebSample/ProtobufWeb/ProtoGen
copy ProtobufWebSample/protos/gen/csharp/EchoData.cs ProtobufWebSample/ProtobufWeb/ProtoGen/EchoData.cs

新增 ProtobufWebSample/ProtobufWeb/Controllers/EchoController.cs,內容如下:

using Microsoft.AspNetCore.Mvc;
using ProtobufWeb.ProtoGen;

namespace ProtobufWeb.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EchoController : ControllerBase
    {
        // POST api/Echo
        [HttpPost]
        public ActionResult<echodata> Post([FromBody] EchoData value)
        {
            value.Text = $"ECHO: {value.Text}";
            return value;
        }
    }
}

要能接收和傳送 protobuf 格式,要建立 formatter

ProtobufWeb/Formatters/ProtobufFormatter.cs

namespace ProtobufWeb.Formatters
{
    using Google.Protobuf;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Formatters;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Net.Http.Headers;
    using System.Threading.Tasks;
    using System.Collections.Generic;
    using System;

    public static class ServicesConfiguration
    {
        public static void AddProtobufFormatter(this IServiceCollection services)
        {
            services.Configure<mvcoptions>(options =&gt;
            {
                options.InputFormatters.Add(new ProtobufInputFormatter(new ProtobufFormatterOptions()));
                options.OutputFormatters.Add(new ProtobufOutputFormatter(new ProtobufFormatterOptions()));
                options.FormatterMappings.SetMediaTypeMappingForFormat("protobuf", Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/x-protobuf"));
            });
        }
    }

    public class ProtobufFormatterOptions
    {
        public HashSet<string> SupportedContentTypes { get; set; } = new HashSet<string> { "application/x-protobuf", "application/protobuf", "application/x-google-protobuf" };

        public HashSet<string> SupportedExtensions { get; set; } = new HashSet<string> { "proto" };

        public bool SuppressReadBuffering { get; set; } = false;
    }

    public class ProtobufInputFormatter : InputFormatter
    {
        private readonly ProtobufFormatterOptions _options;

        public ProtobufInputFormatter(ProtobufFormatterOptions protobufFormatterOptions)
        {
            _options = protobufFormatterOptions ?? throw new ArgumentNullException(nameof(protobufFormatterOptions));
            foreach (var contentType in protobufFormatterOptions.SupportedContentTypes)
            {
                SupportedMediaTypes.Add(new MediaTypeHeaderValue(contentType));
            }
        }

        public override Task<inputformatterresult> ReadRequestBodyAsync(InputFormatterContext context)
        {
            try
            {
                var request = context.HttpContext.Request;
                var obj = (IMessage)Activator.CreateInstance(context.ModelType);
                obj.MergeFrom(request.Body);

                return InputFormatterResult.SuccessAsync(obj);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception: " + ex);
                return InputFormatterResult.FailureAsync();
            }
        }
    }

    public class ProtobufOutputFormatter : OutputFormatter
    {
        private readonly ProtobufFormatterOptions _options;
        public string ContentType { get; private set; }

        public ProtobufOutputFormatter(ProtobufFormatterOptions protobufFormatterOptions)
        {
            ContentType = "application/x-protobuf";
            _options = protobufFormatterOptions ?? throw new ArgumentNullException(nameof(protobufFormatterOptions));
            foreach (var contentType in protobufFormatterOptions.SupportedContentTypes)
            {
                SupportedMediaTypes.Add(new MediaTypeHeaderValue(contentType));
            }
        }

        public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
        {
            var response = context.HttpContext.Response;

            // Proto-encode
            var protoObj = context.Object as IMessage;
            var serialized = protoObj.ToByteArray();

            return response.Body.WriteAsync(serialized, 0, serialized.Length);
        }
    }
}

到 startup.cs 加入 services.AddProtobufFormatter();,startup.cs 完整內容如下:

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;
using ProtobufWeb.Formatters;

namespace ProtobufWeb
{
    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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            services.AddProtobufFormatter();
        }

        // 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
            {
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            DefaultFilesOptions options = new DefaultFilesOptions();
            options.DefaultFileNames.Clear();
            options.DefaultFileNames.Add("index.html");
            app.UseDefaultFiles(options);

            app.UseStaticFiles();
            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}

建立測試頁 ProtobufWebSample/ProtobufWeb/wwwroot/index.html

mkdir wwwroot
mkdir wwwroot/scripts

將 bundle.js 移到 wwwroot/scripts 資料夾

建立 index.html 內容如下

<!--DOCTYPE html-->



    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="scripts/bundle.js"></script>



    <script>
        var obj = new proto.EchoData();
        obj.setText('hello world');
        obj.setAge(10);
        var data = search.serializeBinary();

        var xhr = new XMLHttpRequest();
        xhr.open('POST', '/api/Echo', true);
        xhr.withCredentials = false;
        xhr.responseType = 'arraybuffer';
        xhr.setRequestHeader('Content-Type', 'application/protobuf');
        xhr.setRequestHeader('Accept', 'application/protobuf');
        xhr.addEventListener('readystatechange', (function (xhr) {
            return function handleReady() {
                if (xhr.readyState === xhr.DONE) {
                    xhr.removeEventListener('readystatechange', handleReady, false)
                    console.log('ready', xhr)
                    if (xhr.status >= 200 && xhr.status < 300) {
                        var data = proto.EchoData.deserializeBinary(xhr.response);
                        console.log('ok',data)
                    } else {
                        console.log('catch', xhr)
                    }
                }
            }
        })(xhr), false);
        xhr.send(data);
    </script>

    <script>
        var xhr1 = new XMLHttpRequest();
        xhr1.open('POST', '/api/Echo', true);
        xhr1.withCredentials = false;
        xhr1.responseType = 'json';
        xhr1.setRequestHeader('Content-Type', 'application/json');
        xhr1.setRequestHeader('Accept', 'application/json');
        xhr1.addEventListener('readystatechange', (function (xhr) {
            return function handleReady() {
                if (xhr.readyState === xhr.DONE) {
                    xhr.removeEventListener('readystatechange', handleReady, false)
                    console.log('ready', xhr)
                    if (xhr.status >= 200 && xhr.status < 300) {
                        console.log(xhr.response)
                    } else {
                        console.log('catch', xhr)
                    }
                }
            }
        })(xhr1), false);
        xhr1.send(JSON.stringify({
            text: 'hello world 2',
            age: 20
        }));
    </script>

執行網站,啟動 開發者工具查看結果

啟動網站

dotnet run

瀏覽器開啟 http://localhost:5001/index.html

使用開發者工具查看 Network 欄位的傳送結果

參考資料

adsense