Trong buổi học trước chúng ta đã làm quen với hệ thống web. Chúng ta đã cài đặt hai chương trình web server IIS và Apache. Trong bài học này chúng ta sẽ xây dựng các chương trình client và server đơn giản sử dụng kỹ thuật lập trình socket với giao thức HTTP.
Xây dựng web client đơn giản
Thực hành
Trong bài thực hành này, chúng ta sẽ cùng xây dựng một web client đơn giản nhất. Web client này chỉ có thể gửi một truy vấn về server. Khi nhận phản hồi nó sẽ in kết quả ra giao diện console.
(1) Tạo project C# thuộc loại Console App. Bạn có thể lựa chọn kiểu ứng dụng Console App (dành cho .NET 5 trở lên) hoặc Console App (.NET Framework) đều được. Đặt tên cho project là HttpClient_Console.
(2) Mở file Program.cs và viết code như sau:
using System; using System.Net; using System.Net.Sockets; using System.IO; namespace HttpClient { class Program { static void Main(string[] args) { TcpClient http_client = new TcpClient(); http_client.Connect(IPAddress.Loopback, 80); StreamWriter sw = new StreamWriter(http_client.GetStream()); var request = "GET /\r\nHost: localhost"; sw.WriteLine(request); sw.Flush(); StreamReader sr = new StreamReader(http_client.GetStream()); var response = sr.ReadToEnd(); Console.WriteLine(response); sw.Close(); sr.Close(); Console.ReadKey(); } } }
(3) Dịch và chạy chương trình. Giả sử bạn đang sử dụng server IIS, kết quả sẽ như sau:
Đây là mã HTML mà chương trình chủ IIS gửi lại cho chương trình qua HTTP.
Nếu sử dụng Apache, kết quả thu được có thể sẽ khác.
Phân tích
Trong ví dụ trên, chúng ta đã thực hiện các thao tác sau:
(1) Thiết lập một liên kết TCP tới cổng số 80 của máy tính cục bộ:
TcpClient http_client = new TcpClient();
http_client.Connect(IPAddress.Loopback, 80);
(2) Trên liên kết TCP này, chúng ta gửi đi một chuỗi văn bản theo cấu trúc của HTTP request:
StreamWriter sw = new StreamWriter(http_client.GetStream());
var request = "GET /\r\nHost: localhost";
sw.WriteLine(request);
sw.Flush();
Ở đây chúng ta sử dụng Stream Adapter StreamWriter để tiện lợi cho việc gửi từng dòng văn bản lên luồng mạng TCP.
(3) Sau khi gửi đi chuỗi truy vấn, chúng ta đọc toàn bộ phản hồi về chuỗi html và in ra màn hình console:
StreamReader sr = new StreamReader(http_client.GetStream());
var response = sr.ReadToEnd();
Console.WriteLine(response);
Ở đây, chúng ta sử dụng Stream Adapter StreamReader để tiện lợi cho việc đọc cả chuỗi văn bản lớn từ luồng mạng TCP.
Như vậy, chúng ta có thể thấy, chương trình do chúng ta viết đã có thể trao đổi thông tin với web server IIS / Apache thông qua giao thức HTTP.
Đây là ví dụ đơn giản nhất về việc tạo gói tin truy vấn HTTP và nhận phản hồi từ server.
Lưu ý, mặc dù bạn có thể tải nội dung trang web HTML về ứng dụng, bạn không thể hiển thị nó như trong trình duyệt. HTML bản chất chỉ là chuỗi ký tự và nó cần các cơ chế của trình duyệt để có thể hiển thị thành giao diện đồ họa.
Xây dựng web server đơn giản
Trong phần này, chúng ta sẽ cùng xây dựng một web server đơn giản. Web server này có thể nhận truy vấn từ trình duyệt thực sự (Chrome, Edge, Firefox, v.v.) và trả về một đoạn HTML.
Thực hành
(1) Tạo một dự án mới kiểu Console App, đặt tên là HttpServerSimple.
(2) Mở file Program.cs và viết code như sau:
using System; using System.Net; using System.Net.Sockets; using System.IO; namespace HttpServerSimple { class Program { static void Main(string[] args) { TcpListener listener = new TcpListener(IPAddress.Any, 80); listener.Start(5); while (true) { try { Console.WriteLine("Waiting for connection ..."); var client = listener.AcceptTcpClient(); var sr = new StreamReader(client.GetStream()); string request = sr.ReadLine(); Console.WriteLine(request); var sw = new StreamWriter(client.GetStream()); sw.WriteLine("HTTP/1.1 200 OK\n"); sw.WriteLine("<html><head></head><body><h1>Hello world</h1><body></html>"); sw.Flush(); client.Close(); } catch (Exception) { } } } } }
(3) Kiểm tra xem IIS hay Apache có hoạt động hay không. Nếu các chương trình này hoạt động thì cần tắt đi.
(4) Dịch và chạy thử chương trình.
(5) Mở một trình duyệt bất kỳ và gõ vào đường link: http://localhost. Bạn sẽ thu được kết quả như sau:
Như vậy chương trình server đơn giản bạn viết thực sự có thể tiếp nhận truy vấn từ trình duyệt và trả lại kết quả cho trình duyệt.
Phân tích
Trong ví dụ đơn giản trên, chúng ta đã thực hiện những thao tác sau:
(1) Khởi tạo một TCP socket gắn với mọi giao diện mạng trên máy cục bộ và lắng nghe cổng TCP/80
TcpListener listener = new TcpListener(IPAddress.Any, 80);
listener.Start(5);
(2) Tiếp nhận yêu cầu kết nối TCP từ trình duyệt (hoặc bất kỳ chương trình nào khác)
var client = listener.AcceptTcpClient();
(3) Đọc dòng request line của truy vấn HTTP sử dụng adapter StreamReader
var sr = new StreamReader(client.GetStream());
string request = sr.ReadLine();
(4) Tạo một chuỗi phản hồi đơn giản chứa status line và body là một chuỗi HTML, và gửi chuỗi phản hồi này trở lại cho client.
var sw = new StreamWriter(client.GetStream());
sw.WriteLine("HTTP/1.1 200 OK\n");
sw.WriteLine("<html><head></head><body><h1>Hello world</h1><body></html>");
Như vậy, bạn có thể thấy, chương trình do chúng ta viết ra có thể tương tác với trình duyệt thông qua giao thức HTTP. Bạn cũng có thể thấy, chuỗi HTML cũng được chứa bên trong phản hồi. Bên trong phản hồi này có thể chứa mã JavaScript, CSS, hoặc bất kỳ thứ gì có thể chuyển thành văn bản.
Xây dựng web server phức tạp hơn
Trong ví dụ này chúng ta sẽ cùng xây dựng một web server “phức tạp” hơn một chút. Web server này có khả năng lưu trữ các file html, javascript và css. Khi trình duyệt yêu cầu file nào (thông qua URL), server sẽ tìm file tương ứng và trả về cho trình duyệt.
(1) Tạo một project mới thuộc loại Console App và đặt tên là HttpServer
(2) Mở file Program.cs và code như sau:
using System; using System.Net; using System.Net.Sockets; using System.IO; namespace HttpServer { class Program { static void Main(string[] args) { TcpListener listener = new TcpListener(IPAddress.Any, 80); listener.Start(5); while (true) { try { Console.WriteLine("Waiting for connection ..."); TcpClient client = listener.AcceptTcpClient(); StreamReader sr = new StreamReader(client.GetStream()); StreamWriter sw = new StreamWriter(client.GetStream()); // đọc dòng truy vấn string request = sr.ReadLine(); string[] tokens = request.Split(' '); string method = tokens[0]; string url = tokens[1]; switch (method.ToUpper()) { // truy vấn url = site1/ case "GET": if (url.EndsWith("/")) url += "index.html"; // site1/index.html string filename = "C:\\htdocs\\" + url; // htdocs\site1/index.html, \ \ \ if (File.Exists(filename)) { sr = new StreamReader(filename); sw.WriteLine("HTTP/1.1 200 OK\n"); sw.WriteLine(sr.ReadToEnd()); sw.Flush(); } else { sw.WriteLine("HTTP/1.1 404 Not Found"); sw.WriteLine("<h1>Page not found</h1>"); sw.Flush(); } break; default: break; } Console.WriteLine(request); client.Close(); } catch (Exception) { } } } } }
(3) Dịch và chạy chương trình.
(4) Tạo thư mục C:\htdocs. Trong thư mục này tạo file index.html với nội dung như sau:
<!DOCTYPE html> <html> <head> <title>Home page</title> </head> <body> <a href="about.html">About my website</a> <h1>Welcome to my website</h1> <p>PowerTCP Sockets for .NET is an enhanced collection of sockets controls, boasting a newly designed API that is intuitive and easy to use. The new design promotes a Model-View-Controller (MVC) application architecture, so that you may localize your protocol functions and settings to a reusable model class. This library requires fewer resources, provides greater flexibility, elicits code that is easier to re-use and maintain, and is backed by the best technical support in the industry.</p> </body> </html>
Mở trình duyệt và nhập vào địa chỉ http://localhost/. Bạn sẽ thu được kết quả như sau:
(5) Tạo tiếp file about.html trong thư mục htdocs và nhập nội dung như sau:
<!DOCTYPE html> <html> <head> <title>Home page</title> </head> <body> <a href="about.html">About my website</a> <h1>About my website</h1> <p>Test page for custom server</p> </body> </html>
Mở trình duyệt và nhập vào url http://localhost/about.html
Tương tự, bạn có thể tạo thêm bất kỳ file html (css + js) nào trong thư mục htdocs và truy xuất qua url. Bạn cũng có thể tạo thêm các thư mục con bên trong thư mục htdocs
Như vậy, web server đơn giản của bạn đã có thể phục vụ file theo yêu cầu của trình duyệt. Đây cũng là cách thức hoạt động cơ bản của chương trình chủ web.
+ Nếu bạn thấy site hữu ích, trước khi rời đi hãy giúp đỡ site bằng một hành động nhỏ để site có thể phát triển và phục vụ bạn tốt hơn.
+ Nếu bạn thấy bài viết hữu ích, hãy giúp chia sẻ tới mọi người.
+ Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần thảo luận cuối trang.
Cảm ơn bạn!
Thư viện WebClient
Trong phần trên chúng ta đã trực tiếp sử dụng kỹ thuật lập trình socket để tạo và xử lý gói tin HTTP. Điều này giúp chúng ta hiểu rõ về giao thức HTTP. Tuy nhiên, đây không phải là cách thức phù hợp để phát triển các ứng dụng khai thác HTTP.
Nền tảng .NET cung cấp một số thư viện để làm việc với giao thức HTTP mà chúng ta có thể trực tiếp sử dụng, bao gồm thư viện WebClient và thư viện Httpclient. WebClient là thư viện lâu đời và khá đơn giản. HttpClient là thư viện được phát triển sau và hiện đang được sử dụng phổ biến hơn. Trong bài học này, chúng ta sẽ xem xét thư viện WebClient trước. Thư viện HttpClient sẽ được xem xét trong bài học về Web API.
Chúng ta cùng thực hiện một ví dụ.
Bước 1. Tạo một dự án loại ConsoleApp đặt tên là WebClientSample
Bước 2. Viết code cho Program.cs như sau:
using System.Net; namespace ConsoleApp1 { internal class Program { static void Main(string[] args) { Console.WriteLine("Hello, World!"); WebClient client = new(); string reply = client.DownloadString("https://vnexpress.net"); File.WriteAllText("vnexpress.net.html", reply); Console.WriteLine(reply); Console.ReadLine(); } } }
Khi chạy, chương trình sẽ tải toàn bộ HTML của trang vnexpress.net và lưu vào file vnexpress.net.html rồi in ra màn hình console.
Khi mở thư mục Debug bạn sẽ tìm thấy file vnexpress.net.html. Bạn có thể mở file này bằng trình duyệt và sẽ thấy giao diện trang chủ của vnexpress.net.
Qua ví dụ này bạn có thể thấy, làm việc với giao thức HTTP giờ trở nên rất đơn giản. Bạn chỉ cần tạo một object của lớp WebClient
WebClient client = new();
sau đó gọi phương thức DownloadString từ object này để tải dữ liệu từ server.
Phương thức này yêu cầu cung cấp một đường dẫn Url. Toàn bộ việc tạo truy vấn HTTP và giải mã phần thân của phản hồi HTTP sẽ được phương thức này tự động xử lý. Bạn thậm chí không cần biết đến cấu trúc các gói tin của HTTP.
Một điểm đặc biệt nữa là lớp WebClient hỗ trợ cả HTTP và HTTPS. Hầu hết các web server hiện đại chỉ chấp nhận truy vấn có mã hóa HTTPS. Đây cũng là lý do ban đầu để thử nghiệm tạo truy vấn HTTP, chúng ta tự cài đặt server riêng chứ không dùng các server đang hoạt động trên Internet.
Kết luận
- Trong bài học này chúng ta đã làm quen với kỹ thuật lập trình cơ bản với giao thức HTTP. Có những điểm sau cần lưu ý:
- Giao thức HTTP có hai loại gói tin: gói truy vấn và gói phản hồi
- Cả hai loại gói tin HTTP đều dựa trên văn bản, tức là những chuỗi ký tự được định dạng đặc biệt
- Giao thức HTTP sử dụng dịch vụ truyền thông của TCP. Do vậy có thể sử dụng tất cả kỹ thuật lập trình với TCP socket đã biết để lập trình với HTTP.
Chi tiết về giao thức HTTP sẽ được xem xét trong bài học tiếp theo – lý thuyết về giao thức HTTP.
Do HTTP là giao thức rất phổ biến, trên .NET có nhiều thư viện hỗ trợ xử lý HTTP. Trong bài học sau chúng ta sẽ làm quen với một số thư viện như vậy.