Panduan ini menjelaskan cara menggunakan Simple Log Service SDK untuk iOS guna mengumpulkan data log secara cepat.
Prasyarat
Anda telah menginstal iOS SDK. Untuk detailnya, lihat Instal iOS SDK.
Panduan cepat
Inisialisasi SDK dan panggil metode addLog untuk mengirim log.
iOS SDK mendukung inisialisasi beberapa instans. Instans
LogProducerConfigdanLogProducerClientharus digunakan berpasangan.Saat mengirim log ke Simple Log Service, Anda harus menggunakan Pasangan Kunci Akses dari Akun Alibaba Cloud atau Pengguna RAM untuk mengotentikasi permintaan dan mencegah perubahan tidak sah. Untuk mencegah risiko keamanan akibat kredensial yang terpapar, jangan menyematkan Pasangan Kunci Akses secara langsung di aplikasi seluler Anda. Sebagai gantinya, kami menyarankan menggunakan layanan transfer log langsung untuk perangkat seluler guna mengonfigurasi Pasangan Kunci Akses. Untuk informasi lebih lanjut, lihat Bangun layanan untuk mengunggah log dari perangkat seluler ke Simple Log Service.
@interface ProducerExampleController ()
// Kami menyarankan agar instans LogProducerConfig dan LogProducerClient disimpan sebagai properti global.
@property(nonatomic, strong) LogProducerConfig *config;
@property(nonatomic, strong) LogProducerClient *client;
@end
@implementation ProducerExampleController
// Callback bersifat opsional. Gunakan untuk melacak status pengiriman log.
// Untuk mengonfigurasi Pasangan Kunci Akses secara dinamis, atur callback agar diperbarui saat dipanggil.
static void _on_log_send_done(const char * config_name, log_producer_result result, size_t log_bytes, size_t compressed_bytes, const char * req_id, const char * message, const unsigned char * raw_buffer, void * userparams) {
if (result == LOG_PRODUCER_OK) {
NSString *success = [NSString stringWithFormat:@"send success, config : %s, result : %d, log bytes : %d, compressed bytes : %d, request id : %s", config_name, (result), (int)log_bytes, (int)compressed_bytes, req_id];
SLSLogV("%@", success);
} else {
NSString *fail = [NSString stringWithFormat:@"send fail , config : %s, result : %d, log bytes : %d, compressed bytes : %d, request id : %s, error message : %s", config_name, (result), (int)log_bytes, (int)compressed_bytes, req_id, message];
SLSLogV("%@", fail);
}
}
- (void) initLogProducer {
// Titik akhir layanan. Nilainya harus diawali dengan https:// atau http://.
NSString *endpoint = @"<your-endpoint>";
NSString *project = @"<your-project>";
NSString *logstore = @"<your-logstore>";
_config = [[LogProducerConfig alloc] initWithEndpoint:endpoint
project:project
logstore:logstore
];
// Atur topik log.
[_config SetTopic:@"example_topic"];
// Tambahkan tag global, yang akan dilampirkan ke semua log.
[_config AddTag:@"example" value:@"example_tag"];
// Menentukan apakah log yang kedaluwarsa akan dibuang. 0: Simpan log dan perbarui timestamp-nya ke waktu saat ini. 1 (Default): Buang log.
[_config SetDropDelayLog:1];
// Menentukan apakah log yang gagal otentikasi akan dibuang. 0 (Default): Simpan log. 1: Buang log.
[_config SetDropUnauthorizedLog:0];
// Untuk memantau status pengiriman log, berikan callback.
_client = [[LogProducerClient alloc] initWithLogProducerConfig:_config callback:_on_log_send_done];
}
// Meminta Pasangan Kunci Akses.
- (void) requestAccessKey {
// Kami menyarankan menggunakan layanan transfer log langsung untuk perangkat seluler guna mengonfigurasi Pasangan Kunci Akses.
// ...
// Setelah memperoleh Pasangan Kunci Akses, panggil metode ini untuk memperbaruinya.
[self updateAccessKey:accessKeyId accessKeySecret:accessKeySecret securityToken:securityToken];
}
// Perbarui Pasangan Kunci Akses.
- (void) updateAccessKey:(NSString *)accessKeyId accessKeySecret:(NSString *)accessKeySecret securityToken:(NSString *)securityToken {
// Untuk Pasangan Kunci Akses dari Security Token Service (STS) yang mencakup token keamanan, gunakan metode ini untuk memperbarui kredensial.
if (securityToken.length > 0) {
if (accessKeyId.length > 0 && accessKeySecret.length > 0) {
[_config ResetSecurityToken:accessKeyId
accessKeySecret:accessKeySecret
securityToken:securityToken
];
}
} else {
// Untuk Pasangan Kunci Akses yang tidak menggunakan token keamanan, gunakan metode ini.
if (accessKeyId.length > 0 && accessKeySecret.length > 0) {
[_config setAccessKeyId: accessKeyId];
[_config setAccessKeySecret: accessKeySecret];
}
}
}
// Kirim log.
- (void) addLog {
Log *log = [Log log];
// Sesuaikan field log sesuai kebutuhan bisnis Anda.
[log putContent:@"content_key_1" intValue:123456];
[log putContent:@"content_key_2" floatValue:23.34f];
[log putContent:@"content_key_3" value:@"Karakter Tionghoa"];
[_client AddLog:log];
}
@end</your-logstore></your-project></your-endpoint>Penggunaan lanjutan
Konfigurasi parameter secara dinamis
iOS SDK memungkinkan Anda mengonfigurasi parameter seperti Project, Logstore, endpoint, dan Pasangan Kunci Akses secara dinamis. Untuk informasi lebih lanjut tentang cara memperoleh endpoint, lihat Endpoint. Untuk informasi lebih lanjut tentang cara memperoleh Pasangan Kunci Akses, lihat Pasangan Kunci Akses.
Konfigurasikan endpoint, Project, dan Logstore secara dinamis.
// Anda dapat mengonfigurasi parameter endpoint, Project, dan Logstore secara independen atau bersamaan. // Perbarui endpoint. [_config setEndpoint:@"your new-endpoint"]; // Perbarui Project. [_config setProject:@"your new-project"]; // Perbarui Logstore. [_config setLogstore:@"your new-logstore"];Konfigurasikan Pasangan Kunci Akses secara dinamis.
Saat mengonfigurasi Pasangan Kunci Akses secara dinamis, kami menyarankan menggunakan callback.
// Jika Anda telah menginisialisasi callback saat menginisialisasi LogProducerClient, Anda dapat mengabaikan kode berikut. static void _on_log_send_done(const char * config_name, log_producer_result result, size_t log_bytes, size_t compressed_bytes, const char * req_id, const char * message, const unsigned char * raw_buffer, void * userparams) { if (LOG_PRODUCER_SEND_UNAUTHORIZED == result || LOG_PRODUCER_PARAMETERS_INVALID) { [selfClzz requestAccessKey]; // selfClzz adalah referensi ke instans kelas saat ini, ditangkap untuk digunakan dalam callback bergaya C. } } // Jika Anda ingin memeriksa status pengiriman log, berikan callback sebagai parameter kedua. _client = [[LogProducerClient alloc] initWithLogProducerConfig:_config callback:_on_log_send_done]; // Meminta Pasangan Kunci Akses. - (void) requestAccessKey { // Kami menyarankan agar Anda terlebih dahulu menggunakan layanan transfer log langsung dari perangkat seluler untuk mengonfigurasi Pasangan Kunci Akses. // ... // Setelah memperoleh Pasangan Kunci Akses, perbarui kredensial. [self updateAccessKey:accessKeyId accessKeySecret:accessKeySecret securityToken:securityToken]; } // Perbarui Pasangan Kunci Akses. - (void) updateAccessKey:(NSString *)accessKeyId accessKeySecret:(NSString *)accessKeySecret securityToken:(NSString *)securityToken { // Jika Anda memperoleh Pasangan Kunci Akses dengan menggunakan STS, maka Pasangan Kunci Akses tersebut berisi token keamanan. Dalam hal ini, Anda harus memperbarui Pasangan Kunci Akses dengan cara berikut. if (securityToken.length > 0) { if (accessKeyId.length > 0 && accessKeySecret.length > 0) { [_config ResetSecurityToken:accessKeyId accessKeySecret:accessKeySecret securityToken:securityToken ]; } } else { // Jika Anda tidak memperoleh Pasangan Kunci Akses dengan menggunakan STS, perbarui Pasangan Kunci Akses dengan cara berikut. if (accessKeyId.length > 0 && accessKeySecret.length > 0) { [_config setAccessKeyId: accessKeyId]; [_config setAccessKeySecret: accessKeySecret]; } } }Konfigurasikan sumber log, topik log, dan tag secara dinamis.
PentingAnda tidak dapat mengonfigurasi sumber log, topik log, dan tag untuk log individual. Pengaturan ini bersifat global dan berlaku untuk semua log yang belum dikirim, termasuk yang ada di buffer percobaan ulang. Penerapan global ini dapat menyebabkan perilaku tak terduga jika Anda menggunakan parameter ini untuk melacak kategori log tertentu. Untuk melacak kategori, kami menyarankan menambahkan field kustom ke setiap log.
// Atur topik log. [_config SetTopic:@"your new-topic"]; // Atur sumber log. [_config SetSource:@"your new-source"]; // Atur tag. Tag ini akan dilampirkan ke setiap log. [_config AddTag:@"test" value:@"your new-tag"];
Unggah yang dapat dilanjutkan
iOS SDK mendukung unggah yang dapat dilanjutkan. Saat diaktifkan, fitur ini menyimpan log yang dikirimkan melalui metode addLog ke file log biner (binlog) lokal. SDK hanya menghapus data lokal ini setelah log berhasil dikirim, sehingga menjamin pengiriman setidaknya sekali (at-least-once delivery).
Untuk mengaktifkan unggah yang dapat dilanjutkan, tambahkan kode berikut selama inisialisasi SDK.
Saat menginisialisasi beberapa instans LogProducerConfig, Anda harus memberikan path file unik ke metode
setPersistentFilePathuntuk setiap instansLogProducerConfig.Jika aplikasi Anda menggunakan beberapa proses dengan unggah yang dapat dilanjutkan diaktifkan, inisialisasi SDK hanya pada proses utama. Jika proses anak juga perlu mengumpulkan data, pastikan path file yang diberikan ke metode
SetPersistentFilePathunik untuk setiap proses. Jika tidak, data log dapat menjadi tidak teratur atau hilang.Menginisialisasi LogProducerConfig berulang kali dari beberapa thread dapat menyebabkan masalah.
- (void) initLogProducer {
// Nilai 1 mengaktifkan unggah yang dapat dilanjutkan, dan 0 menonaktifkannya. Nilai default adalah 0.
[_config SetPersistent:1];
// Nama file persistensi. Pastikan direktori untuk file ini tersedia.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *Path = [[paths lastObject] stringByAppendingString:@"/log.dat"];
[_config SetPersistentFilePath:Path];
// Jumlah file persistensi yang akan dirotasi. Kami menyarankan nilai 10.
[_config SetPersistentMaxFileCount:10];
// Ukuran setiap file persistensi dalam byte, ditentukan sebagai N*1024*1024. Kami menyarankan nilai N antara 1 hingga 10.
[_config SetPersistentMaxFileSize:N*1024*1024];
// Jumlah maksimum log yang dapat disimpan secara lokal. Jangan melebihi 1.048.576. Default: 65.536.
[_config SetPersistentMaxLogCount:65536];
}Parameter konfigurasi
Semua parameter konfigurasi disediakan oleh kelas LogProducerConfig. Tabel berikut menjelaskan parameter-parameter tersebut.
Parameter | Type | Deskripsi |
SetTopic | String | Menentukan nilai untuk field topik. Nilai default adalah string kosong. |
AddTag | String | Menentukan tag untuk log dalam format |
SetSource | String | Menentukan nilai untuk field sumber. Nilai default adalah |
SetPacketLogBytes | Int | Menentukan ukuran maksimum paket log dalam buffer. Paket yang melebihi batas ini akan segera dikirim. Rentang nilai: 1 hingga 5.242.880 byte. Default: 1.048.576 byte (1 MiB). |
SetPacketLogCount | Int | Menentukan jumlah maksimum log per paket. Jika batas ini terlampaui, paket log akan segera dikirim. Rentang nilai: 1 hingga 4.096. Default: 1.024. |
SetPacketTimeout | Int | Menentukan waktu maksimum paket log dapat tetap berada di buffer. Paket akan segera dikirim ketika timeout ini tercapai. Default: 3.000 milidetik. |
SetMaxBufferLimit | Int | Menentukan ukuran buffer maksimum dalam byte untuk instans produsen. Jika batas ini terlampaui, pemanggilan Default: 67.108.864 (64 MiB). |
SetPersistent | Int | Mengaktifkan atau menonaktifkan fitur unggah yang dapat dilanjutkan.
|
SetPersistentFilePath | String | Menentukan path file untuk persistensi data. Direktori harus tersedia. Setiap instans Nilai default adalah string kosong. |
SetPersistentForceFlush | Int | Menentukan apakah akan memaksa flush ke file persistensi setelah setiap pemanggilan
Kami menyarankan opsi ini untuk skenario dengan keandalan tinggi. |
SetPersistentMaxFileCount | Int | Menentukan jumlah maksimum file persistensi untuk rotasi. Nilai 0 menonaktifkan rotasi dan menggunakan satu file saja. Rentang yang disarankan: 1 hingga 10. Default: 0. |
SetPersistentMaxFileSize | Int | Menentukan ukuran maksimum setiap file persistensi, dalam byte. Nilai yang disarankan antara 1.048.576 byte (1 MiB) hingga 10.485.760 byte (10 MiB). |
SetPersistentMaxLogCount | Int | Menentukan jumlah maksimum log yang dapat disimpan secara lokal untuk persistensi. Nilai tidak boleh melebihi 1.048.576. Default: 65.536. |
SetConnectTimeoutSec | Int | Menentukan timeout koneksi jaringan, dalam detik. Nilai default adalah 10. |
SetSendTimeoutSec | Int | Menentukan timeout untuk mengirim paket log, dalam detik. Nilai default adalah 15. |
SetDestroyFlusherWaitSec | Int | Menentukan waktu tunggu maksimum dalam detik untuk thread flusher dihancurkan. Nilai default adalah 1. |
SetDestroySenderWaitSec | Int | Menentukan waktu tunggu maksimum dalam detik untuk kolam thread pengirim dihancurkan. Nilai default adalah 1. |
SetCompressType | Int | Menentukan jenis kompresi data untuk unggahan.
|
SetNtpTimeOffset | Int | Menentukan offset antara waktu perangkat dan waktu NTP standar, dalam detik. Ini dihitung sebagai |
SetMaxLogDelayTime | Int | Menentukan selisih waktu maksimum yang diizinkan antara timestamp log dan waktu lokal perangkat, dalam detik. Jika selisih ini terlampaui, log akan diproses berdasarkan pengaturan |
SetDropDelayLog | Int | Menentukan apakah akan membuang log yang melebihi batas
|
SetDropUnauthorizedLog | Int | Menentukan apakah akan membuang log yang gagal otentikasi.
|
Kode kesalahan
Semua kode kesalahan didefinisikan dalam log_producer_result. Tabel berikut menjelaskan setiap kode kesalahan beserta solusinya.
Kode kesalahan | Nilai | Deskripsi | Solusi |
LOG_PRODUCER_OK | 0 | Berhasil. | Tidak berlaku. |
LOG_PRODUCER_INVALID | 1 | SDK telah dihancurkan atau tidak valid. |
|
LOG_PRODUCER_WRITE_ERROR | 2 | Gagal menulis data. Kesalahan ini dapat terjadi jika traffic penulisan untuk Project telah mencapai batasnya. | Tingkatkan batas traffic penulisan untuk Project. Untuk informasi lebih lanjut, lihat Sesuaikan kuota sumber daya. |
LOG_PRODUCER_DROP_ERROR | 3 | Cache disk atau memori penuh. | Tingkatkan nilai parameter |
LOG_PRODUCER_SEND_NETWORK_ERROR | 4 | Terjadi kesalahan jaringan. | Verifikasi konfigurasi Endpoint, Project, dan Logstore. |
LOG_PRODUCER_SEND_QUOTA_ERROR | 5 | Kuota traffic penulisan untuk Project telah terlampaui. | Tingkatkan batas traffic penulisan untuk Project. Untuk informasi lebih lanjut, lihat Sesuaikan kuota sumber daya. |
LOG_PRODUCER_SEND_UNAUTHORIZED | 6 | AccessKey kedaluwarsa, tidak valid, atau kebijakan aksesnya salah dikonfigurasi. | Verifikasi AccessKey. Pengguna RAM harus memiliki izin untuk mengelola sumber daya Simple Log Service. Untuk informasi lebih lanjut, lihat Berikan izin kepada Pengguna RAM. |
LOG_PRODUCER_SEND_SERVER_ERROR | 7 | Terjadi kesalahan di sisi server. | Hubungi dukungan teknis. |
LOG_PRODUCER_SEND_DISCARD_ERROR | 8 | Data dibuang, biasanya karena perbedaan waktu antara perangkat dan server. | SDK secara otomatis mencoba ulang permintaan. |
LOG_PRODUCER_SEND_TIME_ERROR | 9 | Jam perangkat tidak tersinkronisasi dengan waktu server. | SDK secara otomatis mengoreksi masalah ini. |
LOG_PRODUCER_SEND_EXIT_BUFFERED | 10 | SDK dihancurkan sebelum semua data cache berhasil dikirim. | Untuk mencegah kehilangan data, aktifkan fitur unggah yang dapat dilanjutkan. |
LOG_PRODUCER_PARAMETERS_INVALID | 11 | Parameter inisialisasi SDK tidak valid. | Verifikasi parameter seperti AccessKey, Endpoint, Project, dan Logstore. |
LOG_PRODUCER_PERSISTENT_ERROR | 99 | Gagal menulis data cache ke disk. | 1. Pastikan path file cache benar. 2. Periksa apakah file cache penuh. 3. Pastikan ruang disk yang tersedia mencukupi. |
FAQ
Mengapa terjadi duplikasi log?
iOS SDK mengirim log secara asinkron. Masalah jaringan dapat menyebabkan kegagalan transmisi dan percobaan ulang. SDK hanya mengonfirmasi pengiriman setelah menerima kode status 200. Proses ini dapat menyebabkan duplikasi log. Gunakan kueri SQL untuk menghapus duplikat data saat analisis.
Jika Anda mengamati tingkat duplikasi log yang tinggi, periksa adanya masalah dalam inisialisasi SDK. Penyebab umum dan solusinya meliputi:
Konfigurasi unggah yang dapat dilanjutkan salah
Untuk mengatasinya, verifikasi bahwa path file yang diberikan ke metode
SetPersistentFilePathbersifat unik secara global.Inisialisasi SDK berulang
Inisialisasi SDK berulang sering disebabkan oleh penerapan pola singleton yang tidak tepat. Inisialisasi SDK seperti pada contoh berikut.
// AliyunLogHelper.h @interface AliyunLogHelper : NSObject + (instancetype)sharedInstance; - (void) addLog:(Log *)log; @end // AliyunLogHelper.m @interface AliyunLogHelper () @property(nonatomic, strong) LogProducerConfig *config; @property(nonatomic, strong) LogProducerClient *client; @end @implementation AliyunLogHelper + (instancetype)sharedInstance { static AliyunLogHelper *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance; } - (instancetype)init { self = [super init]; if (self) { [self initLogProducer]; } return self; } - (void) initLogProducer { // Ganti dengan kode inisialisasi Anda. _config = [[LogProducerConfig alloc] initWithEndpoint:@"" project:@"" logstore:@"" accessKeyID:@"" accessKeySecret:@"" securityToken:@"" ]; _client = [[LogProducerClient alloc] initWithLogProducerConfig:_config callback:_on_log_send_done]; } - (void) addLog:(Log *)log { if (nil == log) { return; } [_client AddLog:log]; } @endPenggunaan beberapa proses juga dapat menyebabkan inisialisasi berulang. Inisialisasi SDK hanya pada proses utama. Jika Anda harus menginisialisasi SDK di berbagai proses, pastikan keunikan dengan mengatur nilai berbeda untuk
SetPersistentFilePathdi setiap proses.
Mengoptimalkan konfigurasi untuk lingkungan jaringan lemah
Jika aplikasi Anda beroperasi di lingkungan jaringan lemah, optimalkan konfigurasi SDK seperti di bawah ini.
// Inisialisasi SDK. - (void) initProducer() { // Sesuaikan timeout koneksi dan pengiriman HTTP untuk membantu mengurangi tingkat duplikasi log. // Anda dapat menyesuaikan nilai timeout sesuai kebutuhan. [_config SetConnectTimeoutSec:20]; [_config SetSendTimeoutSec:20]; // Parameter inisialisasi lainnya. // ... }
Menangani kehilangan log
Pelaporan log merupakan proses asinkron. Jika aplikasi ditutup sebelum pelaporan selesai, kehilangan log dapat terjadi. Kami menyarankan mengaktifkan fitur unggah yang dapat dilanjutkan. Untuk informasi lebih lanjut, lihat Unggah yang dapat dilanjutkan.
Menangani keterlambatan pelaporan log
Kondisi jaringan dan kasus penggunaan aplikasi tertentu dapat menyebabkan keterlambatan pelaporan log. Jika keterlambatan hanya terjadi pada beberapa perangkat, hal ini biasanya normal. Jika tidak, identifikasi masalahnya menggunakan kode kesalahan dalam tabel berikut.
Kode kesalahan | Deskripsi |
LOG_PRODUCER_SEND_NETWORK_ERROR | Verifikasi bahwa Endpoint, Project, dan Logstore dikonfigurasi dengan benar. |
LOG_PRODUCER_SEND_UNAUTHORIZED | Verifikasi bahwa kunci akses valid, belum kedaluwarsa, dan kebijakan izin terkait dikonfigurasi dengan benar. |
LOG_PRODUCER_SEND_QUOTA_ERROR | Kuota traffic penulisan untuk Project telah terlampaui. Untuk informasi lebih lanjut, lihat Sesuaikan kuota sumber daya. |
Kebijakan pre-resolusi DNS dan cache
Ya. Contoh berikut menunjukkan cara menggunakan iOS SDK dengan HTTPDNS SDK untuk menerapkan kebijakan pre-resolusi DNS dan cache.
Catatan: Jika Endpoint SLS menggunakan nama domain HTTPS, Anda harus merujuk ke Solusi untuk skenario HTTPS dan SNI.
Implementasikan NSURLProtocol kustom.
#import <Foundation/Foundation.h> #import "HttpDnsNSURLProtocolImpl.h" #import <arpa/inet.h> #import <zlib.h> #import <objc/runtime.h> static NSString *const hasBeenInterceptedCustomLabelKey = @"HttpDnsHttpMessagePropertyKey"; static NSString *const kAnchorAlreadyAdded = @"AnchorAlreadyAdded"; @interface HttpDnsNSURLProtocolImpl () <NSStreamDelegate> @property (strong, readwrite, nonatomic) NSMutableURLRequest *curRequest; @property (strong, readwrite, nonatomic) NSRunLoop *curRunLoop; @property (strong, readwrite, nonatomic) NSInputStream *inputStream; @property (nonatomic, assign) BOOL responseIsHandle; @property (assign, nonatomic) z_stream gzipStream; @end @implementation HttpDnsNSURLProtocolImpl - (instancetype)initWithRequest:(NSURLRequest *)request cachedResponse:(nullable NSCachedURLResponse *)cachedResponse client:(nullable id <NSURLProtocolClient>)client { self = [super initWithRequest:request cachedResponse:cachedResponse client:client]; if (self) { _gzipStream.zalloc = Z_NULL; _gzipStream.zfree = Z_NULL; if (inflateInit2(&_gzipStream, 16 + MAX_WBITS) != Z_OK) { [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:@"gzip initialize fail" code:-1 userInfo:nil]]; } } return self; } /** * Menentukan apakah akan mencegat dan memproses permintaan tertentu. * * @param request Permintaan tertentu. * @return YES untuk mencegat permintaan, atau NO untuk meneruskannya. */ + (BOOL)canInitWithRequest:(NSURLRequest *)request { if([[request.URL absoluteString] isEqual:@"about:blank"]) { return NO; } // Mencegah loop tak hingga yang dapat terjadi jika permintaan dicegat ulang. if ([NSURLProtocol propertyForKey:hasBeenInterceptedCustomLabelKey inRequest:request]) { return NO; } NSString * url = request.URL.absoluteString; NSString * domain = request.URL.host; // Hanya mencegat permintaan HTTPS. if (![url hasPrefix:@"https"]) { return NO; } // Anda dapat menambahkan kondisi lain, seperti mengonfigurasi array host untuk hanya mencegat host dalam array tersebut. // Hanya mencegat permintaan yang host-nya diganti dengan alamat IP. if (![self isPlainIpAddress:domain]) { return NO; } return YES; } + (BOOL)isPlainIpAddress:(NSString *)hostStr { if (!hostStr) { return NO; } // Memeriksa apakah alamat tersebut merupakan alamat IPv4. const char *utf8 = [hostStr UTF8String]; int success = 0; struct in_addr dst; success = inet_pton(AF_INET, utf8, &dst); if (success == 1) { return YES; } // Memeriksa apakah alamat tersebut merupakan alamat IPv6. struct in6_addr dst6; success = inet_pton(AF_INET6, utf8, &dst6); if (success == 1) { return YES; } return NO; } // Untuk mengalihkan permintaan atau menambahkan header, lakukan operasi dalam metode ini. + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { return request; } // Mulai memuat permintaan. - (void)startLoading { NSMutableURLRequest *request = [self.request mutableCopy]; // Menandai permintaan sebagai telah diproses untuk mencegah loop tak hingga. [NSURLProtocol setProperty:@(YES) forKey:hasBeenInterceptedCustomLabelKey inRequest:request]; self.curRequest = [self createNewRequest:request]; [self startRequest]; } - (NSString *)cookieForURL:(NSURL *)URL { NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; NSMutableArray *cookieList = [NSMutableArray array]; for (NSHTTPCookie *cookie in [cookieStorage cookies]) { if (![self p_checkCookie:cookie URL:URL]) { continue; } [cookieList addObject:cookie]; } if (cookieList.count > 0) { NSDictionary *cookieDic = [NSHTTPCookie requestHeaderFieldsWithCookies:cookieList]; if ([cookieDic objectForKey:@"Cookie"]) { return cookieDic[@"Cookie"]; } } return nil; } - (BOOL)p_checkCookie:(NSHTTPCookie *)cookie URL:(NSURL *)URL { if (cookie.domain.length <= 0 || URL.host.length <= 0) { return NO; } if ([URL.host containsString:cookie.domain]) { return YES; } return NO; } - (NSMutableURLRequest *)createNewRequest:(NSURLRequest*)request { NSURL* originUrl = request.URL; NSString *cookie = [self cookieForURL:originUrl]; NSMutableURLRequest* mutableRequest = [request copy]; [mutableRequest setValue:cookie forHTTPHeaderField:@"Cookie"]; return [mutableRequest copy]; } /** * Membatalkan permintaan. */ - (void)stopLoading { if (_inputStream.streamStatus == NSStreamStatusOpen) { [self closeStream:_inputStream]; } [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:@"stop loading" code:-1 userInfo:nil]]; } /** * Meneruskan permintaan menggunakan CFHTTPMessage. */ - (void)startRequest { // Informasi header dari permintaan asli. NSDictionary *headFields = _curRequest.allHTTPHeaderFields; CFStringRef url = (__bridge CFStringRef) [_curRequest.URL absoluteString]; CFURLRef requestURL = CFURLCreateWithString(kCFAllocatorDefault, url, NULL); // Metode dari permintaan asli, seperti GET atau POST. CFStringRef requestMethod = (__bridge_retained CFStringRef) _curRequest.HTTPMethod; // Membuat objek CFHTTPMessageRef berdasarkan URL, metode, dan versi permintaan. CFHTTPMessageRef cfrequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, requestURL, kCFHTTPVersion1_1); // Menambahkan data yang dilampirkan pada permintaan HTTP POST. CFStringRef requestBody = CFSTR(""); CFDataRef bodyData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, requestBody, kCFStringEncodingUTF8, 0); if (_curRequest.HTTPBody) { bodyData = (__bridge_retained CFDataRef) _curRequest.HTTPBody; } else if (_curRequest.HTTPBodyStream) { NSData *data = [self dataWithInputStream:_curRequest.HTTPBodyStream]; NSString *strBody = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"originStrBody: %@", strBody); CFDataRef body = (__bridge_retained CFDataRef) data; CFHTTPMessageSetBody(cfrequest, body); CFRelease(body); } else { CFHTTPMessageSetBody(cfrequest, bodyData); } // Menyalin informasi header dari permintaan asli. for (NSString* header in headFields) { CFStringRef requestHeader = (__bridge CFStringRef) header; CFStringRef requestHeaderValue = (__bridge CFStringRef) [headFields valueForKey:header]; CFHTTPMessageSetHeaderFieldValue(cfrequest, requestHeader, requestHeaderValue); } // Membuat aliran input untuk objek CFHTTPMessage. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, cfrequest); #pragma clang diagnostic pop self.inputStream = (__bridge_transfer NSInputStream *) readStream; // Mengatur informasi host Indikasi Nama Server (SNI). Ini adalah langkah kunci. NSString *host = [_curRequest.allHTTPHeaderFields objectForKey:@"host"]; if (!host) { host = _curRequest.URL.host; } [_inputStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey]; NSDictionary *sslProperties = [[NSDictionary alloc] initWithObjectsAndKeys: host, (__bridge id) kCFStreamSSLPeerName, nil]; [_inputStream setProperty:sslProperties forKey:(__bridge_transfer NSString *) kCFStreamPropertySSLSettings]; [_inputStream setDelegate:self]; if (!_curRunLoop) { // Menyimpan runloop dari thread saat ini. Ini sangat penting untuk permintaan yang dialihkan. self.curRunLoop = [NSRunLoop currentRunLoop]; } // Menjadwalkan aliran pada runloop saat ini. [_inputStream scheduleInRunLoop:_curRunLoop forMode:NSRunLoopCommonModes]; [_inputStream open]; CFRelease(cfrequest); CFRelease(requestURL); cfrequest = NULL; CFRelease(bodyData); CFRelease(requestBody); CFRelease(requestMethod); } - (NSData*)dataWithInputStream:(NSInputStream*)stream { NSMutableData *data = [NSMutableData data]; [stream open]; NSInteger result; uint8_t buffer[1024]; while ((result = [stream read:buffer maxLength:1024]) != 0) { if (result > 0) { // buffer berisi result byte data yang perlu ditangani [data appendBytes:buffer length:result]; } else if (result < 0) { // Aliran mengalami kesalahan. Anda dapat memperoleh objek NSError menggunakan [iStream streamError] data = nil; break; } } [stream close]; return data; } #pragma mark - NSStreamDelegate /** * Menangani event aliran. */ - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { if (eventCode == NSStreamEventHasBytesAvailable) { CFReadStreamRef readStream = (__bridge_retained CFReadStreamRef) aStream; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" CFHTTPMessageRef message = (CFHTTPMessageRef) CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader); #pragma clang diagnostic pop if (CFHTTPMessageIsHeaderComplete(message)) { NSInputStream *inputstream = (NSInputStream *) aStream; NSNumber *alreadyAdded = objc_getAssociatedObject(aStream, (__bridge const void *)(kAnchorAlreadyAdded)); NSDictionary *headDict = (__bridge NSDictionary *) (CFHTTPMessageCopyAllHeaderFields(message)); if (!alreadyAdded || ![alreadyAdded boolValue]) { objc_setAssociatedObject(aStream, (__bridge const void *)(kAnchorAlreadyAdded), [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_COPY); // Memberi tahu klien bahwa respons telah diterima. Pemberitahuan ini hanya dikirim sekali. CFStringRef httpVersion = CFHTTPMessageCopyVersion(message); // Mendapatkan kode status dari header respons. CFIndex statusCode = CFHTTPMessageGetResponseStatusCode(message); if (!self.responseIsHandle) { NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:_curRequest.URL statusCode:statusCode HTTPVersion:(__bridge NSString *) httpVersion headerFields:headDict]; [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; self.responseIsHandle = YES; } // Memverifikasi sertifikat. SecTrustRef trust = (__bridge SecTrustRef) [aStream propertyForKey:(__bridge NSString *) kCFStreamPropertySSLPeerTrust]; SecTrustResultType res = kSecTrustResultInvalid; NSMutableArray *policies = [NSMutableArray array]; NSString *domain = [[_curRequest allHTTPHeaderFields] valueForKey:@"host"]; if (domain) { [policies addObject:(__bridge_transfer id) SecPolicyCreateSSL(true, (__bridge CFStringRef) domain)]; } else { [policies addObject:(__bridge_transfer id) SecPolicyCreateBasicX509()]; } // Mengikat kebijakan verifikasi ke sertifikat server. SecTrustSetPolicies(trust, (__bridge CFArrayRef) policies); if (SecTrustEvaluate(trust, &res) != errSecSuccess) { [self closeStream:aStream]; [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:@"can not evaluate the server trust" code:-1 userInfo:nil]]; return; } if (res != kSecTrustResultProceed && res != kSecTrustResultUnspecified) { // Jika verifikasi sertifikat gagal, tutup aliran input. [self closeStream:aStream]; [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:@"fail to evaluate the server trust" code:-1 userInfo:nil]]; } else { // Sertifikat telah diverifikasi. if (statusCode >= 300 && statusCode < 400) { // Menangani kode kesalahan pengalihan. [self closeStream:aStream]; [self handleRedirect:message]; } else { NSError *error = nil; NSData *data = [self readDataFromInputStream:inputstream headerDict:headDict stream:aStream error:&error]; if (error) { [self.client URLProtocol:self didFailWithError:error]; } else { [self.client URLProtocol:self didLoadData:data]; } } } } else { NSError *error = nil; NSData *data = [self readDataFromInputStream:inputstream headerDict:headDict stream:aStream error:&error]; if (error) { [self.client URLProtocol:self didFailWithError:error]; } else { [self.client URLProtocol:self didLoadData:data]; } } CFRelease((CFReadStreamRef)inputstream); CFRelease(message); } } else if (eventCode == NSStreamEventErrorOccurred) { [self closeStream:aStream]; inflateEnd(&_gzipStream); // Memberi tahu klien bahwa terjadi kesalahan. [self.client URLProtocol:self didFailWithError: [[NSError alloc] initWithDomain:@"NSStreamEventErrorOccurred" code:-1 userInfo:nil]]; } else if (eventCode == NSStreamEventEndEncountered) { CFReadStreamRef readStream = (__bridge_retained CFReadStreamRef) aStream; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" CFHTTPMessageRef message = (CFHTTPMessageRef) CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader); #pragma clang diagnostic pop if (CFHTTPMessageIsHeaderComplete(message)) { NSNumber *alreadyAdded = objc_getAssociatedObject(aStream, (__bridge const void *)(kAnchorAlreadyAdded)); NSDictionary *headDict = (__bridge NSDictionary *) (CFHTTPMessageCopyAllHeaderFields(message)); if (!alreadyAdded || ![alreadyAdded boolValue]) { objc_setAssociatedObject(aStream, (__bridge const void *)(kAnchorAlreadyAdded), [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_COPY); // Memberi tahu klien bahwa respons telah diterima. Pemberitahuan ini hanya dikirim sekali. if (!self.responseIsHandle) { CFStringRef httpVersion = CFHTTPMessageCopyVersion(message); // Mendapatkan kode status dari header respons. CFIndex statusCode = CFHTTPMessageGetResponseStatusCode(message); NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:_curRequest.URL statusCode:statusCode HTTPVersion:(__bridge NSString *) httpVersion headerFields:headDict]; [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; self.responseIsHandle = YES; } } } [self closeStream:_inputStream]; inflateEnd(&_gzipStream); [self.client URLProtocolDidFinishLoading:self]; } } - (NSData *)readDataFromInputStream:(NSInputStream *)inputStream headerDict:(NSDictionary *)headDict stream:(NSStream *)aStream error:(NSError **)error { // Jika header respons tidak lengkap. UInt8 buffer[16 * 1024]; NSInteger length = [inputStream read:buffer maxLength:sizeof(buffer)]; if (length < 0) { *error = [[NSError alloc] initWithDomain:@"inputstream length is invalid" code:-2 userInfo:nil]; [aStream removeFromRunLoop:_curRunLoop forMode:NSRunLoopCommonModes]; [aStream setDelegate:nil]; [aStream close]; return nil; } NSData *data = [[NSData alloc] initWithBytes:buffer length:length]; if (headDict[@"Content-Encoding"] && [headDict[@"Content-Encoding"] containsString:@"gzip"]) { data = [self gzipUncompress:data]; if (!data) { *error = [[NSError alloc] initWithDomain:@"can't read any data" code:-3 userInfo:nil]; return nil; } } return data; } - (void)closeStream:(NSStream*)stream { [stream removeFromRunLoop:_curRunLoop forMode:NSRunLoopCommonModes]; [stream setDelegate:nil]; [stream close]; } - (void)handleRedirect:(CFHTTPMessageRef)messageRef { // Header respons. CFDictionaryRef headerFieldsRef = CFHTTPMessageCopyAllHeaderFields(messageRef); NSDictionary *headDict = (__bridge_transfer NSDictionary *)headerFieldsRef; [self redirect:headDict]; } - (void)redirect:(NSDictionary *)headDict { // Jika cookie diperlukan untuk pengalihan, tangani hal tersebut. NSString *location = headDict[@"Location"]; if (!location) location = headDict[@"location"]; NSURL *url = [[NSURL alloc] initWithString:location]; _curRequest.URL = url; if ([[_curRequest.HTTPMethod lowercaseString] isEqualToString:@"post"]) { // Sesuai RFC, permintaan POST yang dialihkan harus diubah menjadi permintaan GET. _curRequest.HTTPMethod = @"GET"; _curRequest.HTTPBody = nil; } [self startRequest]; } - (NSData *)gzipUncompress:(NSData *)gzippedData { if ([gzippedData length] == 0) { return gzippedData; } unsigned full_length = (unsigned) [gzippedData length]; unsigned half_length = (unsigned) [gzippedData length] / 2; NSMutableData *decompressed = [NSMutableData dataWithLength:full_length + half_length]; BOOL done = NO; int status; _gzipStream.next_in = (Bytef *)[gzippedData bytes]; _gzipStream.avail_in = (uInt)[gzippedData length]; _gzipStream.total_out = 0; while (_gzipStream.avail_in != 0 && !done) { if (_gzipStream.total_out >= [decompressed length]) { [decompressed increaseLengthBy:half_length]; } _gzipStream.next_out = (Bytef *)[decompressed mutableBytes] + _gzipStream.total_out; _gzipStream.avail_out = (uInt)([decompressed length] - _gzipStream.total_out); status = inflate(&_gzipStream, Z_SYNC_FLUSH); if (status == Z_STREAM_END) { done = YES; } else if (status == Z_BUF_ERROR) { // Jika Z_BUF_ERROR disebabkan oleh buffer output yang tidak mencukupi, buffer input belum sepenuhnya diproses dan buffer output penuh. Dalam hal ini, loop harus dilanjutkan untuk memperluas buffer. // Kondisi sebaliknya menunjukkan bahwa kesalahan tidak disebabkan oleh buffer output yang tidak mencukupi. Dalam hal ini, loop harus dihentikan, yang menunjukkan adanya kesalahan. if (_gzipStream.avail_in == 0 || _gzipStream.avail_out != 0) { return nil; } } else if (status != Z_OK) { return nil; } } [decompressed setLength:_gzipStream.total_out]; return [NSData dataWithData:decompressed]; } @endImplementasikan BeforeSend kustom.
- (void)setupHttpDNS:(NSString *)accountId { // Konfigurasi HTTPDNS. HttpDnsService *httpdns = [[HttpDnsService alloc] initWithAccountID:accountId]; [httpdns setHTTPSRequestEnabled:YES]; [httpdns setPersistentCacheIPEnabled:YES]; [httpdns setReuseExpiredIPEnabled:YES]; [httpdns setIPv6Enabled:YES]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSMutableArray *protocolsArray = [NSMutableArray arrayWithArray:configuration.protocolClasses]; // Atur NSURLProtocol kustom yang didefinisikan pada langkah sebelumnya. [protocolsArray insertObject:[HttpDnsNSURLProtocolImpl class] atIndex:0]; [configuration setProtocolClasses:protocolsArray]; NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:nil delegateQueue:nil]; [SLSURLSession setURLSession:session]; [SLSURLSession setBeforeSend:^NSMutableURLRequest * _Nonnull(NSMutableURLRequest * _Nonnull request) { NSURL *url = request.URL; // Anda dapat menambahkan kondisi di sini untuk menerapkan logika ini hanya ke URL tertentu. HttpdnsResult *result = [httpdns resolveHostSync:url.host byIpType:HttpdnsQueryIPTypeAuto]; if (!result) { return request; } NSString *ipAddress = nil; if (result.hasIpv4Address) { ipAddress = result.firstIpv4Address; } else if(result.hasIpv6Address) { ipAddress = result.firstIpv6Address; } else { return request; } NSString *requestUrl = url.absoluteString; requestUrl = [requestUrl stringByReplacingOccurrencesOfString: url.host withString:ipAddress]; [request setURL:[NSURL URLWithString:requestUrl]]; [request setValue:url.host forHTTPHeaderField:@"host"]; return request; }]; }Lengkapi inisialisasi SDK SLS.
@implementation AliyunSLS - (void)initSLS { // Atur HTTPDNS kustom untuk SDK SLS. // Catatan: // Pengaturan ini berlaku untuk semua instans SDK SLS. [self setupHttpDNS:accountId]; [self initProducer]; } - (void)initProducer { // Inisialisasi LogProducerConfig dan LogProducerClient seperti biasa. // ... } @end