خادم HTTP
إنشاء خادم HTTP باستخدام الصنف HttpListener
يُستخدَم النوع HttpListener
لإنشاء مُستمِع (listener) مُبسط للرد على طلبات HTTP. نُنشِئ نسخة من هذا النوع كالتالي:
listener = new HttpListener();
تُستخدَم الخاصية Prefixes
لتخصيص الرابط (url) الذي يَستمِع إليه الخادم والذي ستُرسَل إليه طلبات الـ HTTP.
listener.Prefixes.Add("http://*:" + port + "/"); listener.Start();
عندما يَستلِم الخادم طَلَبًا (http request) مُعينًا، فإنه بالضرورة يحتاج إلى معلومات عن الطلب حتى يقوم بمُعالجته. تَتوَفر تلك المعلومات من خلال التابع GetContext()
.
var context = listener.GetContext(); var request = context.Request; response = context.Response;
نظرًا لأن الهدف من خادم الملفات هو إرسال الملفات عند طلبها، سيقوم الخادم أولًا بتحديد اسم الملف المطلوب:
var fileName = request.RawUrl.Substring(1);
ثم يُرسِل مُحتويات الملف إلى مَجْرى مَتْن الرد (response body) كالتالي:
using (var fileStream = File.OpenRead(fullFilePath)) { response.ContentType = "application/octet-stream"; response.ContentLength64 = (new FileInfo(fullFilePath)).Length; response.AddHeader("Content-Disposition", "Attachment; filename=\"" + Path.GetFileName(fullFilePath) + "\""); fileStream.CopyTo(response.OutputStream); } response.OutputStream.Close();
المثال بالكامل:
using System; using System.IO; using System.Net; class HttpFileServer { private static HttpListenerResponse response; private static HttpListener listener; private static string baseFilesystemPath; static void Main(string[] args) { if (!HttpListener.IsSupported) { Console.WriteLine( "*** HttpListener requires at least Windows XP SP2 or Windows Server 2003."); return; } if(args.Length < 2) { Console.WriteLine("Basic read-only HTTP file server"); Console.WriteLine(); Console.WriteLine("Usage: httpfileserver <base filesystem path> <port>"); Console.WriteLine("Request format: http://url:port/path/to/file.ext"); return; } baseFilesystemPath = Path.GetFullPath(args[0]); var port = int.Parse(args[1]); listener = new HttpListener(); listener.Prefixes.Add("http://*:" + port + "/"); listener.Start(); Console.WriteLine("--- Server stated, base path is: " + baseFilesystemPath); Console.WriteLine("--- Listening, exit with Ctrl-C"); try { ServerLoop(); } catch(Exception ex) { Console.WriteLine(ex); if(response != null) { SendErrorResponse(500, "Internal server error"); } } } static void ServerLoop() { while(true) { var context = listener.GetContext(); var request = context.Request; response = context.Response; var fileName = request.RawUrl.Substring(1); Console.WriteLine("--- Got {0} request for: {1}", request.HttpMethod, fileName); if (request.HttpMethod.ToUpper() != "GET") { SendErrorResponse(405, "Method must be GET"); continue; } var fullFilePath = Path.Combine(baseFilesystemPath, fileName); if(!File.Exists(fullFilePath)) { SendErrorResponse(404, "File not found"); continue; } Console.Write(" Sending file..."); using (var fileStream = File.OpenRead(fullFilePath)) { response.ContentType = "application/octet-stream"; response.ContentLength64 = (new FileInfo(fullFilePath)).Length; response.AddHeader("Content-Disposition", "Attachment; filename=\"" + Path.GetFileName(fullFilePath) + "\""); fileStream.CopyTo(response.OutputStream); } response.OutputStream.Close(); response = null; Console.WriteLine("Ok!"); } } static void SendErrorResponse(int statusCode, string statusResponse) { response.ContentLength64 = 0; response.StatusCode = statusCode; response.StatusDescription = statusResponse; response.OutputStream.Close(); Console.WriteLine("*** Sent error: {0} {1}", statusCode, statusResponse); } }
إنشاء خادم HTTP باستخدام ASP.NET Core
بالمثل، نُنشِئ خادم ببروتوكول HTTP لقراءة الملفات (file server) مُشابه للمثال بالأعلى لكن باستخدام بيئة عمل ASP.NET Core
المتطورة.
أولًا: اِنشِئ مجلد فارغ، سنُضيف إليه ملفات المشروع المُنشئة خلال الخطوات التالية.
ثانيًا: اِنشِئ ملف باسم project.json
وأَضِف إليه المحتويات التالية:
{ "dependencies": { "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final", "Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final" }, "commands": { "web": "Microsoft.AspNet.Server.Kestrel --server.urls http://localhost:60000" }, "frameworks": { "dnxcore50": { } }, "fileServer": { "rootDirectory": "c:\\users\\username\\Documents" } }
ثالثًا: اِنشِئ ملف باسم Startup.cs
وأضف إليه الشيفرة التالية:
using System; using Microsoft.AspNet.Builder; using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.StaticFiles; using Microsoft.Extensions.Configuration; public class Startup { public void Configure(IApplicationBuilder app) { var builder = new ConfigurationBuilder(); builder.AddJsonFile("project.json"); var config = builder.Build(); var rootDirectory = config["fileServer:rootDirectory"]; Console.WriteLine("File server root directory: " + rootDirectory); var fileProvider = new PhysicalFileProvider(rootDirectory); var options = new StaticFileOptions(); options.ServeUnknownFileTypes = true; options.FileProvider = fileProvider; options.OnPrepareResponse = context => { context.Context.Response.ContentType = "application/octet-stream"; context.Context.Response.Headers.Add( "Content-Disposition", $"Attachment; filename=\"{context.File.Name}\""); }; app.UseStaticFiles(options); } }
لاحظ اِستخدَام التابع UseStaticFiles
لتزويد الخادم بخاصية قراءة الملفات الساكنة من مجلد معين.
رابعًا: اِفتَح سطر الأوامر (command prompt) في المجلد الذي قُمت لتوك بإنشائه، ونفذ الأوامر التالية:
dnvm use 1.0.0-rc1-final -r coreclr -p dnu restore
تُنْفَّذ الأوامر بالأعلى مرة واحدة فقط.
خامسًا: شَغِّل الخادم باستخدَام الأمر dnx web
. كلمة web هنا هي مُجرد كلمة مُخصَّصة ضِمْن حَقْل الأوامر commands
بالملف project.json
وتُستخدَم اسمًا تعريفيًا لأمر مُخصَّص، وبالتالي ما يُنْفَّذ بالفعل هو قيمة هذا الحقل بالملف.
`Microsoft.AspNet.Server.Kestrel --server.urls http://localhost:60000
والآن تستطيع إرسال طلبات (requests) إلى الخادم من خلال الرابط http://localhost:60000 وهو نفس الرابط (url) المُخصَّص بالأعلى.
لاحظ أننا لغرض التبسيط قد افترضنا أن أسماء جميع الملفات ستكون بترميز ASCII، بالإضافة إلى أننا لم نُعالِج الأخطاء المُحتمَلة أثناء الولوج للملفات.
عميل HTTP
يَتوَفر الصنف HttpClient
من خلال حزمة مكتبات مايكروسوفت Microsoft HTTP Client Libraries.
إرسال طلب GET باستخدام HttpClient.GetAsync
يُرسِل التابع GetAsync
طلب GET
إلى خَادِم (server) عن طريق رابط يُمرَّر إليه كمُعامِل، ويُعيد قيمة من النوع Task<HttpResponseMessage>
تُمثِل رد الخادم (response). يُمكن قراءة الرد كـسِلسِلة نصية string
باستخدام التابع response.Content.ReadAsStringAsync
، كالمثال التالي:
string requestUri = "http://www.example.com"; string responseData; using (var client = new HttpClient()) { using(var response = client.GetAsync(requestUri).Result) { response.EnsureSuccessStatusCode(); responseData = response.Content.ReadAsStringAsync().Result; } }
إرسال طلب GET باستخدام HttpClient.GetStreamAsync
يُرسِل التابع HttpClient.GetStreamAsync
طلب GET
إلى خَادِم (server) عن طريق رابط يُمرَّر إليه كمُعامِل، ولكنه يُعيد مَتْن الرد (response body) في صورة مَجْرى (stream).
private static async Task DownloadAsync(string fromUrl, string toFile) { using (var fileStream = File.OpenWrite(toFile)) { using (var httpClient = new HttpClient()) { Console.WriteLine("Connecting..."); using (var networkStream = await httpClient.GetStreamAsync(fromUrl)) { Console.WriteLine("Downloading..."); await networkStream.CopyToAsync(fileStream); await fileStream.FlushAsync(); } } } }
يُمكن استدعاء الدالة المُعرَّفة بالأعلى كالتالي:
using System; using System.IO; using System.Linq; using System.Net.Http; using System.Threading.Tasks; class HttpGet { static void Main(string[] args) { try { Run(args).Wait(); } catch (Exception ex) { if (ex is AggregateException) ex = ((AggregateException)ex) .Flatten().InnerExceptions.First(); Console.WriteLine("--- Error: " + (ex.InnerException?.Message ?? ex.Message)); } } static async Task Run(string[] args) { if (args.Length < 2) { Console.WriteLine("Basic HTTP downloader"); Console.WriteLine(); Console.WriteLine("Usage: httpget <url>[<:port>] <file>"); return; } await DownloadAsync(fromUrl: args[0], toFile: args[1]); Console.WriteLine("Done!"); } }
إرسال طلب POST باستخدام HttpClient.SendAsync
يُهيَّّئ الطلب في صورة كائن من النوع HttpRequestMessage
، فمثلًا تُسْنَد قيمة الرابط للخاصية RequestUri
بينما تُسنَد قيمة المَتْن (request body) للخاصية Content
.
يُرسِل التابع SendAsync
طلب POST
إلى الخَادِم مع قيمة الطلب المُهيَّئ، ويُعيد قيمة من النوع Task<HttpResponseMessage>
تُمثِل رد الخادم (response)، كالمثال التالي:
string requestUri = "http://www.example.com"; string requestBodyString = "Request body string."; string contentType = "text/plain"; string requestMethod = "POST"; using (var client = new HttpClient()) { var request = new HttpRequestMessage { RequestUri = requestUri, Method = requestMethod, }; byte[] requestBodyBytes = Encoding.UTF8.GetBytes(requestBodyString); request.Content = new ByteArrayContent(requestBodyBytes); request.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType); HttpResponseMessage result = client.SendAsync(request).Result; result.EnsureSuccessStatusCode(); }
إرسال طلب GET باستخدام HttpWebRequest.GetResponse
يُهيِّئ التابع WebRequest.Create
طلب GET
إلى خادم (server) بمُعامِل الرابط المُمرَّر إليه، ثم يتم الارسال الفعلي بواسطة التابع GetResponse
وتُعاد قيمة من النوع WebResponse
تُمثِل رد الخادم. في المثال التالي:
string requestUri = "http://www.example.com"; string responseData; HttpWebRequest request =(HttpWebRequest)WebRequest.Create(parameters.Uri); WebResponse response = request.GetResponse();
يُمكن تَحويل رد الخَادِم إلى سِلسِلة نصية كالتالي:
using (StreamReader responseReader = new StreamReader(response.GetResponseStream())) { responseData = responseReader.ReadToEnd(); }
إرسال طلب POST باستخدام HttpWebRequest.GetResponse
يُهيِّئ التابع WebRequest.Create
طلب POST
إلى خادم (server) بمُعامِل الرابط المُمرر إليه. قد تحتاج إلى تهيئة مَتْن الطلب (request body) أيضًا. للقيام بذلك، استخدم التابع GetRequestStream
لاستعادة مَتْن الطلب بصورة مَجْرى (stream) يُكتَّب عليه المُحتوَى المطلوب.
بعد انتهاء التهيئة، يتم الارسال الفعلي بواسطة التابع GetResponse
وتُعاد قيمة من النوع WebResponse
تُمثِل رد الخادم. في المثال التالي:
string requestUri = "http://www.example.com"; string requestBodyString = "Request body string."; string contentType = "text/plain"; string requestMethod = "POST"; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri) { Method = requestMethod, ContentType = contentType, }; byte[] bytes = Encoding.UTF8.GetBytes(requestBodyString); Stream stream = request.GetRequestStream(); stream.Write(bytes, 0, bytes.Length); stream.Close(); HttpWebResponse response = (HttpWebResponse)request.GetResponse();
رفع ملفات إلى خادم باستخدام HttpWebRequest.GetResponseAsync
تُرفَّع الملفات إلى الخوادم من خلال إرسال طلب POST إلى الخادم مع إرفاق مُحتوَى الملف ضِمْن مَتْن الطلب.
يُهيِّئ التابع WebRequest.CreateHttp
طلب (request) إلى خادم (server) بمُعامِل الرابط المُمرَّر إليه.
var request = WebRequest.CreateHttp(url);
قد تحتاج إلى تهيئة إضافية للطلب مثل إضافة مَتْن إليه (request body). للقيام بذلك، اِستخدِم التابع GetRequestStream
لاستعادة مَتْن الطلب بصورة مَجْرى (stream). تستطيع الكتابة على المَجْرى مباشرة أو تَضْمِينه داخل كائن من النوع StreamWriter
ثم تَكتِب عليه المُحتوَى المطلوب.
using (var requestStream = request.GetRequestStream()) using (var writer = new StreamWriter(requestStream)) { await writer.WriteAsync(""); }
نَحتَاج لرَفع ملف إلى الخادم، مما يعني كتابة مُحتوَيات هذا الملف على مَجْرى مَتْن الطلب. يُفتَح الملف أولًا في صورة مَجْرى ثم تُنقَل محتوياته لمَجْرى الطلب، كالتالي:
using (var fileStream = File.OpenRead(filename)) await fileStream.CopyToAsync(requestStream);
بعد انتهاء التهيئة، يتم الارسال الفعلي للطلب بواسطة التابع GetResponseAsync
وتُعاد قيمة من النوع Task<WebResponse>
تُمثِل رد الخادم، كالتالي:
var response = (HttpWebResponse) await request.GetResponseAsync();
تستعرض الشيفرة التالية المثال بالكامل:
using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Threading.Tasks; public async Task<string> UploadFile(string url, string filename, Dictionary<string, object> postData) { var request = WebRequest.CreateHttp(url); var boundary = $"{Guid.NewGuid():N}"; request.ContentType = $"multipart/form-data; {nameof(boundary)}={boundary}"; request.Method = "POST"; using (var requestStream = request.GetRequestStream()) using (var writer = new StreamWriter(requestStream)) { foreach (var data in postData) await writer.WriteAsync( $"\r\n--{boundary}\r\nContent-Disposition: " + $"form-data; name=\"{data.Key}\"\r\n\r\n{data.Value}"); await writer.WriteAsync( // file header $"\r\n--{boundary}\r\nContent-Disposition: " + $"form-data; name=\"File\"; filename=\"{Path.GetFileName(filename)}\"\r\n" + "Content-Type: application/octet-stream\r\n\r\n"); await writer.FlushAsync(); using (var fileStream = File.OpenRead(filename)) await fileStream.CopyToAsync(requestStream); await writer.WriteAsync($"\r\n--{boundary}--\r\n"); } using (var response = (HttpWebResponse) await request.GetResponseAsync()) using (var responseStream = response.GetResponseStream()) { if (responseStream == null) return string.Empty; using (var reader = new StreamReader(responseStream)) return await reader.ReadToEndAsync(); } }
إرسال طلب GET باستخدام WebClient.DownloadString
يُرسِل التابع DownloadString
طلب GET
إلى خادم (server) عن طريق مُعامِل الرابط المُمرر إليه، ويُعيد قيمة من النوع string
تُمثِل رد الخادم.
string requestUri = "http://www.example.com"; string responseData; using (var client = new WebClient()) { responseData = client.DownloadString(requestUri); }
إرسال طلب POST باستخدام WebClient.UploadData
يُرسِل التابع UploadData
طلب POST
إلى خادم (server) مع مَتْن الطلب المُمرر له، كالتالي:
string requestUri = "http://www.example.com"; string requestBodyString = "Request body string."; string contentType = "text/plain"; string requestMethod = "POST"; byte[] responseBody; byte[] requestBodyBytes = Encoding.UTF8.GetBytes(requestBodyString); using (var client = new WebClient()) { client.Headers[HttpRequestHeader.ContentType] = contentType; responseBody = client.UploadData(requestUri, requestMethod, requestBodyBytes); }
عميل SMTP لإرسال بريد إلكتروني
يُمكِنك بسهولة إنشاء كائن من النوع MailMessage
بحيث يَحمِل معلومات البريد الإلكتروني المَطلوب إرساله، ثم مَرِّره إلى كائن من النوع SmtpClient
حتى يقوم بالإرسال الفعلي.
يَحتوِي النوع MailMessage
على الخاصيات:
- To
- From
- ReplyToList
- CC
- Bcc
- Subject
- Body
- IsBodyHtml
- Attachments
- Priority والتي يُمكن ضَبْط قيمها كأيّ بريد الكتروني عادي.
using(MailMessage MyMail = new MailMessage()) { MyMail.From = new MailAddress(mailfrom); MyMail.To.Add(mailto); MyMail.ReplyToList.Add(replyto); MyMail.CC.Add(mailcc); MyMail.Bcc.Add(mailbcc); MyMail.Subject = subject; MyMail.IsBodyHtml = true; MyMail.Body = body; MyMail.Priority = MailPriority.Normal; // }
في المقابل، يَحتوِي النوع SmtpClient
على الخاصيات Host
و Port
و Credentials
لتخصيص بيانات خَادِم الـ SMTP المُستخدَم لإرسال البريد الإلكتروني.
SmtpClient smtpMailObj = new SmtpClient(); smtpMailObj.Host = "your host"; smtpMailObj.Port = 25; smtpMailObj.Credentials = new System.Net.NetworkCredential("uid", "pwd");
الشيفرة بالكامل:
public class clsMail { private static bool SendMail(string mailfrom, List<string>replytos, List<string> mailtos, List<string> mailccs, List<string> mailbccs, string body, string subject, List<string> Attachment) { try { using(MailMessage MyMail = new MailMessage()) { MyMail.From = new MailAddress(mailfrom); foreach (string mailto in mailtos) MyMail.To.Add(mailto); if (replytos != null && replytos.Any()) { foreach (string replyto in replytos) MyMail.ReplyToList.Add(replyto); } if (mailccs != null && mailccs.Any()) { foreach (string mailcc in mailccs) MyMail.CC.Add(mailcc); } if (mailbccs != null && mailbccs.Any()) { foreach (string mailbcc in mailbccs) MyMail.Bcc.Add(mailbcc); } MyMail.Subject = subject; MyMail.IsBodyHtml = true; MyMail.Body = body; MyMail.Priority = MailPriority.Normal; if (Attachment != null && Attachment.Any()) { System.Net.Mail.Attachment attachment; foreach (var item in Attachment) { attachment = new System.Net.Mail.Attachment(item); MyMail.Attachments.Add(attachment); } } SmtpClient smtpMailObj = new SmtpClient(); smtpMailObj.Host = "your host"; smtpMailObj.Port = 25; smtpMailObj.Credentials = new System.Net.NetworkCredential("uid", "pwd"); smtpMailObj.Send(MyMail); return true; } } catch { return false; } } }
يُدعم النوع MailMessage
إضافة المُرفَقات من خلال الخاصية Attachments
كالتالي:
using System.Net.Mail; using(MailMessage myMail = new MailMessage()) { Attachment attachment = new Attachment(path); myMail.Attachments.Add(attachment); }
عميل UDP لمزامنة التوقيت باستخدام خادم SNTP
يُمكِن لعميل إرسال طلبات لخادم SNTP لمُزامنة التوقيت مع ذلك الخادم. اطلع على RFC 2030 للمزيد من المعلومات عن بروتوكول SNTP
.
تجهيز طلب SNTP كالتالي:
var sntpRequest = new byte[48]; sntpRequest[0] = 0x23; //LI=0 (no warning), VN=4, Mode=3 (client)
إرسال الطلب من خلال الصنف UDPClient
:
var udpClient = new UdpClient(); udpClient.Client.ReceiveTimeout = 5000; udpClient.Send( dgram: sntpRequest, bytes: sntpRequest.Length, hostname: args[0], port: SntpPort);
مُزامنة التوقيت:
var date = BaseDate.AddSeconds(numberOfSeconds).AddHours(localTimeZoneInHours);
الشيفرة بالكامل:
using System; using System.Globalization; using System.Linq; using System.Net; using System.Net.Sockets; class SntpClient { const int SntpPort = 123; static DateTime BaseDate = new DateTime(1900, 1, 1); static void Main(string[] args) { if(args.Length == 0) { Console.WriteLine("Simple SNTP client"); Console.WriteLine(); Console.WriteLine("Usage: sntpclient <sntp server url> [<local timezone>]"); Console.WriteLine(); Console.WriteLine("<local timezone>: a number between -12 and 12 as hours from UTC"); Console.WriteLine("(append .5 for an extra half an hour)"); return; } double localTimeZoneInHours = 0; if(args.Length > 1) localTimeZoneInHours = double.Parse(args[1], CultureInfo.InvariantCulture); var udpClient = new UdpClient(); udpClient.Client.ReceiveTimeout = 5000; var sntpRequest = new byte[48]; sntpRequest[0] = 0x23; //LI=0 (no warning), VN=4, Mode=3 (client) udpClient.Send( dgram: sntpRequest, bytes: sntpRequest.Length, hostname: args[0], port: SntpPort); byte[] sntpResponse; try { IPEndPoint remoteEndpoint = null; sntpResponse = udpClient.Receive(ref remoteEndpoint); } catch(SocketException) { Console.WriteLine("*** No response received from the server"); return; } uint numberOfSeconds; if(BitConverter.IsLittleEndian) numberOfSeconds = BitConverter.ToUInt32( sntpResponse.Skip(40).Take(4).Reverse().ToArray(), 0); else numberOfSeconds = BitConverter.ToUInt32(sntpResponse, 40); var date = BaseDate.AddSeconds(numberOfSeconds).AddHours(localTimeZoneInHours); Console.WriteLine( $"Current date in server: {date:yyyy-MM-dd HH:mm:ss} UTC{localTimeZoneInHours:+0.#;-0.#;.}"); } }
خادم وعميل TCP لتنشئة برنامج دردشة
باستخدام الأنواع TcpListener
و TcpClient
و NetworkStream
.
using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; class TcpChat { static void Main(string[] args) { if(args.Length == 0) { Console.WriteLine("Basic TCP chat"); Console.WriteLine(); Console.WriteLine("Usage:"); Console.WriteLine("tcpchat server <port>"); Console.WriteLine("tcpchat client <url> <port>"); return; } try { Run(args); } catch(IOException) { Console.WriteLine("--- Connection lost"); } catch(SocketException ex) { Console.WriteLine("--- Can't connect: " + ex.Message); } } static void Run(string[] args) { TcpClient client; NetworkStream stream; byte[] buffer = new byte[256]; var encoding = Encoding.ASCII; if(args[0].StartsWith("s", StringComparison.InvariantCultureIgnoreCase)) { var port = int.Parse(args[1]); var listener = new TcpListener(IPAddress.Any, port); listener.Start(); Console.WriteLine("--- Waiting for a connection..."); client = listener.AcceptTcpClient(); } else { var hostName = args[1]; var port = int.Parse(args[2]); client = new TcpClient(); client.Connect(hostName, port); } stream = client.GetStream(); Console.WriteLine("--- Connected. Start typing! (exit with Ctrl-C)"); while(true) { if(Console.KeyAvailable) { var lineToSend = Console.ReadLine(); var bytesToSend = encoding.GetBytes(lineToSend + "\r\n"); stream.Write(bytesToSend, 0, bytesToSend.Length); stream.Flush(); } if (stream.DataAvailable) { var receivedBytesCount = stream.Read(buffer, 0, buffer.Length); var receivedString = encoding.GetString(buffer, 0, receivedBytesCount); Console.Write(receivedString); } } } }
ترجمة -وبتصرف- للفصول:
- HTTP servers
- HTTP clients
- Upload file and POST data to webserver
- System.Net.Mail
- Networking
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.