NetCoreAndGolang-1

.NET Core; bana göre, bulut teknolojileri, modern web uygulamaları ve mobil servisler geliştirme rekabetine dahil olmuş, Ruby, Python, NodeJs vb. dil ve platformlara ilgi-ihtiyaç duyan .NET geliştiriciler için açık kaynak kodlu ve cross-platform destekli bir ortama olan gereksinimi karşılar.

Böylelikle; C# veya F# programlama dilleri ile Linux, MAC OSx ve Windows işletim sistemlerinde çalışabilen, performanslı, derlenip dağıtılabilir programlar geliştirebilir.

Bir diğer tarafta Google tarafından 2007 yılında duyurulan Golang programlama dili; hızla gelişen, her geçen gün artan popülerliği ve etkin bir topluluk dayanışması ile performanslı sistemler, web uygulamaları, dağıtık servisler vb. geliştirebilmek için birçok alanda tercih edilmektedir. Docker, moby, kubernetes vb. açık kaynak kodlu projeler ve bulut altyapısı sağlayacıları (DigitalOcean, Azure, Amazon) Golang'ın gelişmesine katkı sağlamaya devem ediyor.

Burada ön plana çıkan bir farktan bahsetmek isterim. .NET Core çalışma zamanında CoreCLR olarak adlandırılan bir çalıştırıcıya ihtiyaç duyup C# veya F# ile yazılan programların derlenip bu host ile çalıştırılmasını ister. Golang ise bağımsız olarak derleme sonucu oluşturulan çalıştırılabilir bir program üretir. Golang ile yazılan programın; işletim sistemi tarafından sunulan SDK, API, alt seviye işlemleri ve uyumluluk durumlarını kendiniz işler ve idare edersiniz. (Makefile, cross compiling)

Her iki ortamın Github hesapları (.NET Core, Golang), açık kaynak kodlu projeleri, detaylı dokümantasyon içerikleri (Docs Microsoft, Golang Docs) çok zengin ve kesinlikle incelemeye değer.

Basit bir giriş yapmak istemiştim ama sanırım bu iki platformun özelliklerini anlatmak için ayrı bir yazı dizisi gerektiği aşikar.

Teknolojileri lehimize kullanarak; ihtiyaç duyduğumuz çözümleri üretmek, fanatik yaklaşımlardan uzaklaşarak şartları lehimize çevirebilecek basit ve kestirme yolları tercih etmenin, işlerimizi kolaylaştırdığını çoğu zaman tecrübe ettiğimi söyleyebilirim. Buradan yola çıkarak bu iki ortamın farklı özelliklerinden nasıl yararlandığımızı, .NET Core ve Golang' ın birlikte gerçek bir senaryo üzerinden nasıl işlediğine bakalım.

Deneyim: İç ağımızda hizmet veren bir dosya sunucusunun belirli dizin ve içeriklerinin servisler, web uygulamaları (ASP.NET Core MVC Web Application) vb. hizmetler tarafından kullanılması isteniyor. Bu sunucuda .NET veya .NET Core framework kurulumu veya yükseltmesi yapamadığımızı varsayıyorum. Burada görevi Golang devralıyor.


Bölüm Golang

package main

import (
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
)

func main() {
    dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
    if err != nil {
        log.Fatal(err)
    }

    combined := path.Join(dir, "wwwroot")
    panic(http.ListenAndServe(":1444", http.FileServer(http.Dir(combined))))
}

Yukarıda ki Golang programı, çalıştırıldığı dizinde belirtilen klasörü (wwwroot) ve tüm dizin içeriklerini http://*:1444 HTTP protokolü üzerinden erişilebilir yapar. Bir kaç satır kod ile statik içerik host edebilen, cross-platform bir sunucu programı yazmış olduk.

Bu programı sunucuya atıp terminalden - konsoldan çalıştırabilir ve hizmeti başlatabiliriz. Ben bu programı Windows Server 2012 R2 üzerinde bir windows servis olarak çalıştırıp, işletim sistemi tarafından gerekli durumlarda yeniden başlatılmasını sağlamak istiyorum (restart, exception, power vb.). Bunun için Github' dan Service isimli açık kaynak kodlu bir kütüphaneden yararlanıyorum. Varsayılan olarak derlenen Golang programını bu haliyle windows servis olarak Service Control Manager aracılığıyla kayıt - register edemiyoruz. Bu sebeple programı şu şekilde güncelliyorum:

package main

import (
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"

	"github.com/kardianos/service"
)

var logger service.Logger

type program struct{}

func (p *program) Start(s service.Service) error {
	// Start should not block. Do the actual work async.
	go p.run()
	return nil
}
func (p *program) run() {

	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		log.Fatal(err)
	}
	combined := path.Join(path.Dir(dir), "../wwwroot")
	panic(http.ListenAndServe(":1444", http.FileServer(http.Dir(combined))))
}
func (p *program) Stop(s service.Service) error {
	// Stop should not block. Return with a few seconds.
	return nil
}

func main() {
	svcConfig := &service.Config{
		Name:        "GoStaticServer",
		DisplayName: "Go Static File Server",
		Description: "Static file server",
	}

	prg := &program{}
	s, err := service.New(prg, svcConfig)
	if err != nil {
		log.Fatal(err)
	}

	logger, err = s.Logger(nil)
	if err != nil {
		log.Fatal(err)
	}

	err = s.Run()
	if err != nil {
		logger.Error(err)
	}
}

Böylelikle powershell veya sc.exe ile *.exe yi arka planda çalışan bir servis olarak kaydediyor ve hizmeti başlatıyorum.

powershell
New-Service -Name "GoStaticServer" -BinaryPathName "<path-of-goserver.exe>" -StartupType Automatic
Start-Service -Name "GoStaticServer"

Bölüm .NET Core

ASP.NET Core Mvc web uygulamamız içerisinden belli tip isteklerin dosya (image, pdf vb.) talebi olduğunu çözmek için; uygulamamızda dosya adreslerinin (URL)

/public-fs

segmenti ile başladığını varsayıyorum. Örneğin:

http(s)://hostname/public-fs/sample.pdf
http(s)://hostname/public-fs/sub/tree/webworld.png

Bu adımdan sonra ASP.NET Core Middleware özelliğini kullanarak HTTP GET isteklerini Golang ile yazdığımız dosya sunucusuna şu şekilde aktarabiliriz:

public Task Invoke(HttpContext context)
{
    var method = context.Request.Method;
    if (method == "GET")
    {
        var path = context.Request.Path;
        if (path.StartsWithSegments("/public-fs"))
        {
            return HandleFileServerHttpRequest(context);
        }
    }

    return _next(context);
}

private async Task HandleFileServerHttpRequest(HttpContext context)
{
    var requestMessage = new HttpRequestMessage();
    var requestMethod = context.Request.Method;

    // Copy the request headers
    foreach (var header in context.Request.Headers)
    {
        if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
        {
            requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
        }
    }

    var proxyServer = new Uri(_settings.FileServerProxyAddress);
    requestMessage.RequestUri = new Uri(proxyServer, $"{context.Request.Path}{context.Request.QueryString}");
    requestMessage.Method = new HttpMethod(requestMethod);
    var responseMessage = await HttpFactory.Client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted);
    var statusCode = (int)responseMessage.StatusCode;
    context.Response.StatusCode = statusCode;

    foreach (var header in responseMessage.Headers)
    {
        context.Response.Headers[header.Key] = header.Value.ToArray();
    }

    foreach (var header in responseMessage.Content.Headers)
    {
        context.Response.Headers[header.Key] = header.Value.ToArray();
    }

    // SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response.
    context.Response.Headers.Remove("transfer-encoding");
    await responseMessage.Content.CopyToAsync(context.Response.Body);
}

Burada "public-fs" segmenti ile HTTP isteğinin dosya sunucusu tarafından karşılanacağı kararını verip işlemi Middleware de yakalayıp sonlandırıyoruz.

Proxy gibi çalışan bu istek yönlendirme yöntemi ile istemci tarafına gelmiş olan HTTP isteğine ait tüm bilgileri (Headers, QueryString, Path vb.) Golang sunucusuna aktarıp bu sunucudan dönen cevabı gerçek isteğin cevabı olarak atıyoruz.

Böylelikle HTTP 200 (Ok), HTTP 304 (Not Modified) veya HTTP 404 (Not Found) vb. istek ve cevapları doğru kanal ve yöntemlerle dosya sunucumuz ve istemci uygulaması arasında paylaşmış oluruz.

Bana keyif veren bu çözümü geliştirdiğim ortamdan da biraz bahsetmek isterim. Visual Studio Code; Microsoft tarafından geliştirilen açık kaynak kodlu çok yönlü ve kaliteli bir editör. Küçük ve orta ölçekli .NET Core ve Golang projelerini ana bir dizin altında rahatlıkla yönetip, derleyip debug modda çalıştırdım ve tüm süreçleri kolaylıkla test edebildim.

GolangVsCodeDebug-1

Bu yazıda derinlemesine değinilmesi gereken bir çok konu var. Özellikle çözüm ararken alternatif yollarında göz önünde bulundurulması, tek bir ortamda takılı kalmamak, daha çok şey öğrenmek, keyifle çalışmak... Bunun içinde diğer programlama dillerinin, kütüphanelerin ve hatta mobil geliştirme ortamlarının yüzeyselde olsa farkında olmak, yöntemlerini incelemek sizi bambaşka çözümlere götürüp daha üretken olmanızı sağlayabilir.

Uygulamaların kaynak kodlarına buradan (Github) erişebilirsiniz.