Chắc hẳn ace đều đã, đang và có thể sẽ làm việc liên quan tới xử lí video (video playing, video streaming, video processing, …). Đây là một thế giới với kho kiến thức khổng lồ, hàng trăm, hàng ngàn paper phải đọc. Mình quyết định sẽ viết một series liên quan tới video codec dưới góc độ làm việc của một developer chứ không phải là R&D để tránh lan man.
Thông qua lượt bài này, mình hi vọng mọi người có thể tiếp cận nhanh và tối giản nhất có thể, để không cần phải đọc và hiểu hàng tá các thuật ngữ, các paper mà có thể khiến chúng ta nản ngay từ vòng gửi xe. Mình sẽ không đi quá sâu vào bất kì thành phần nào mà chỉ đưa ra những keyword cơ bản nhất, cũng như cố gắng giải thích dễ hiểu nhất nhằm giúp người đọc nắm bắt nhanh nhất.
Video codec là gì?
Nói một cách đơn giản thì đây là một (hoặc tập hợp) phương pháp mã hoá video. Mỗi video codec có thể được implement dựa vào phần cứng (hardware coding) hoặc phần mềm (software coding). Tất nhiên nếu sử dụng phần cứng thì sẽ luôn có sự tối ưu hơn với performance tốt hơn rất nhiều, nhưng một chipset sẽ không thể implement quá nhiều các loại thuật toán, vì thế nó vẫn luôn tồn tại song hành với software coding. Tuỳ vào cách lập trình của các lập trình viên cũng như sự hỗ trợ từ framework mà hardware coding có thể được ưu tiên hoặc không.
Phần lớn các chuẩn mã hoá đều là lossy compression (nén có hư tổn dữ liệu) nhằm tối ưu dung lượng lưu trữ cũng như tối ưu truyền tải dữ liệu trên internet. Người nén có thể lựa chọn cân bằng chất lượng hình ảnh với dung lượng từ video gốc sao cho phù hợp với nhu cầu của họ cũng như người sử dụng.
Có rất nhiều các loại codec cũng như các loại chuẩn (standard) được phát triển từ xưa tới nay, đến từ nhiều hãng khác nhau (như MPEG, Google, …), tuy nhiên mình sẽ chỉ focus vào nhóm chuẩn của MPEG (Moving Picture Experts Group) vì đây là các chuẩn thông dụng nhất, và cũng được hỗ trợ nhiều nhất ở thời điểm hiện tại.
Sơ lược về cách hoạt động của quá trình nén
Video codec làm gì mà thần thánh tới mức có thể giảm dung lượng video ghê gớm như vậy? Sau đây mình sẽ giải thích qua cơ chế xử lí của nó để các bạn nắm bắt được nhé.
Một video gốc (raw video) bao gồm rất nhiều các frames (raw frame) nối tiếp với nhau. Mỗi raw frame sẽ chứa thông tin chính xác của từng điểm ảnh, vì thế dung lượng của video sẽ bằng dung lượng của frame nhân với số lượng frame. Thật là kinh hoàng nếu nghĩ tới 1 video 4K@60fps dài khoảng 1 tiếng đúng ko? Các đĩa Bluray ngoài rạp coi film phần lớn đều sử dụng các video gốc như vậy (thậm chí là nhiều lớp) để tăng độ chi tiết cũng như trải nghiệm của người xem ở rạp. Đó là lí do vì sao coi film chiếu rạp luôn có cảm giác "thật" hơn là coi film nén trên mạng, mặc dù độ phân giải là như nhau đó.
Sẽ thật lãng phí nếu có một điểm ảnh không thay đổi trong suốt quá trình ghi lại video. Nếu điểm ảnh đó đã không thay đổi, thì chi bằng ta … khỏi lưu nó lại làm gì, phải không? Okay, đây là nguồn gốc quá trình nén của bất kì loại codec nào có hiện nay, chỉ có điều các kiểu nén có sự khác biệt về thuật toán mà thôi.
+-------+ +-------+
| I | | P |
+-------+ +-------+ +-------+
|xxxxxxx| | o | |xoxxxxx|
|xxxxxxx| + | o | -> |xxxxoxx|
|xxxxxxx| | o | |xxxoxxx|
+-------+ +-------+ +-------+
Ta tạm hiểu rằng mỗi video khi bắt đầu sẽ cần có 1 frame gốc để dựng hình, cái này gọi là intraframe (viết tắt là I-frame, hoặc còn được biết với các tên là keyframe). Đây là frame gốc, lưu trữ toàn bộ các điểm ảnh ban đầu để khi giải mã có thể dựng được frame đầu tiên (tất nhiên frame này cũng đã được nén, tương tự như nén ảnh). Sau intraframe là các interframe, mà nó không lưu trữ đủ thông tin các điểm ảnh, chỉ lưu trữ thông tin SỰ KHÁC NHAU giữa 2 frame. Có 2 loại interframe là P-frame (predictive frame) và B-frame (bi-directional predictive frame). P-frame được giải mã dựa trên I-frame hoặc P-frame ngay phía trước nó. B-frame cũng tương tự như P-frame, nhưng nó có thể giải mã dựa trên thông tin của cả frame phía sau nó nữa.
Trong một video sẽ có rất nhiều intraframe, mà tần suất xuất hiện của nó được qui định bằng GOP (hoặc GOV). Mục đích của nó là để giải quyết bài toán seeking, cũng như hạn chế việc loss thông tin trong quá trình giải mã video thì cần sync lại để tránh video bị lỗi quá nhiều. GOP (GOV) là viết tắt của cụm từ Group of Picture (hoặc Group of Video), mô tả số lượng I/P/B frames từ keyframe này tới keyframe tiếp theo. Trong một số hệ thống encoding sẽ không xuất hiện tham số GOP này mà sử dụng tham số I-frame interval. Con số này mô tả rằng sau bao nhiêu giây sẽ xuất hiện 1 I-frame
+-------------------------------------------------------------------------------+
| I | P | P | P | P | P | P | P | P | P | I | P | P | P | P | P | P | P | P | P |
+-------------------------------------------------------------------------------+
| GOP = 10 | GOP = 10 |
+-------------------------------------------------------------------------------+
| 20 frames |
+-------------------------------------------------------------------------------+
Nếu ta cho rằng video trong sơ đồ phía trên có framerate (tốc độ khung hình) là 20fps, vậy đồng nghĩa với việc I-frame interval là 0.5. Quá đơn giản phải không nào 😀
Chất lượng của việc nén sẽ được qui định bằng con số bitrate, với đơn vị thường thấy là kbps (kilobits per sec). Hiểu một cách nôm na thì đây chính là kích thước thực của video trong mỗi giây. Con số này có thể là không đổi cho toàn bộ video (CBR – Constant Bitrate), hoặc cũng có thể thay đổi tuỳ vào thời điểm của video (VBR – Variable Bitrate), được qui định tại thời điểm encode. Giả dụ ta có 1 bài toán đơn giản như sau:
1 video được nén CBR, độ dài là 30s với bitrate = 5120kbps
=> dung lượng của video = 5120 / 8 * 30 = 19200 KB ~ 18.75MB
Như các bạn có thể thấy, dung lượng của video không còn quan tâm tới resolution (độ phân giải), mà dung lượng được quyết định bởi bitrate. Ồ, vậy thì độ phân giải còn có vai trò gì nữa? Tất nhiên là nó vẫn có rỗi. Với kiến thức thông thường, ta biết rằng độ phân giải sẽ quyết định mức độ chi tiết của ảnh. Độ chi tiết càng cao thì lượng thông tin càng lớn. Vì vậy nếu ta giữ nguyên bitrate để nén video với lượng thông tin lớn, thì hiển nhiên chất lượng của nó sẽ kém đi. Đây chính là lí do vì sao đôi khi ta gặp những video 4K nhưng lại có chất lượng kém hơn cả video 2K hay 1080p, đơn giản vì nó có bitrate thấp, không tương xứng với chất lượng hình ảnh.
Như ví dụ dưới đây, mình sẽ minh hoạ 2 trường hợp phổ biến mà ta thường gặp. Cho rằng dữ liệu gốc là không đổi (4K lossless downscale xuống 1K lossless), các bạn thử tự hình dung xem kết quả nào cho chất lượng tốt hơn nhé?
* TH1: nén video 4K với 300 units = bitrate 5120kbps?
* TH2: nén video 1K với 100 units = bitrate 5120kbps?
Trên đây mình đã mô tả một cách cơ bản nhất về quá trình mã hoá video. Các kiến thức trên là cơ bản nhất và nó gần như đúng với mọi loại thuật toán nén, mọi loại chuẩn nén. Các thuật toán đời mới hiện nay chỉ có sự khác biệt về tối ưu hoá chất lượng, dung lượng cũng như performance mà thôi.
Hẹn gặp lại các bạn ở bài viết sau. Nếu có phần nào không đúng, nhờ các bạn góp ý để chúng ta có nhiều hiểu biết hơn nhé.
Trong bài viết này tôi sẽ hướng dẫn các bạn tạo ra một thư viện component của riêng mình. Bạn cũng có thể public thư viện nếu thích. Nhiều bạn sẽ tự hỏi rằng có nhiều thư viện lắm rồi thì viết thêm làm chi. Mục đích mình viết bài này để có thể tự tạo ra một package gồm những common component có thể sử dụng lại được. Kết hợp với việc dùng mono repository gồm nhiều packages trong yarn workspaces, các bạn có thể tách code của mình thành các package cho dễ quản lý. Chúng ta cùng bắt đầu nhé.
Thêm các dependencies cần thiết
react & react-dom
React component mà nên cần 2 thư viện này là đương nhiên rồi.
hieunv@HieuNV uikit % yarn add react react-dom
yarn add v1.22.0
[1/4] ? Resolving packages...
[2/4] ? Fetching packages...
[3/4] ? Linking dependencies...
[4/4] ? Building fresh packages...
success Saved lockfile.
success Saved 3 new dependencies.
info Direct dependencies
├─ [email protected]
└─ [email protected]
info All dependencies
├─ [email protected]
├─ [email protected]
└─ [email protected]
✨ Done in 7.91s.
hieunv@HieuNV uikit % yarn dev
yarn run v1.22.0
$ npx styleguidist server
Loading webpack config from:
/Users/hieunv/Projects/hieunv/uikit/node_modules/react-scripts/config/webpack.config.js
ℹ 「wds」: Project is running at http://localhost:6060/
ℹ 「wds」: webpack output is served from undefined
ℹ 「wds」: Content not from webpack is served from /Users/hieunv/Projects/hieunv/uikit
You can now view your style guide in the browser:
Local: http://localhost:6060/
On your network: http://192.168.1.11:6060/
Sau khi khởi động server xong các bạn truy cập và link http://192.168.1.11:6060/
Do chưa có component nào được định nghĩa nên các bạn sẽ nhận được thông báo như trên.
Bắt đầu viết component đầu tiên thôi nào
styleguidist sẽ tự động đọc các components trong thư mục src/components/* và README.md trong thư mục component tương ứng để generate ra document sử dụng react-docgen trên styleguidist server
Loading indicator có lẽ là component huyền thoại mà dự án nào cũng cần đến. Hôm nay mình sẽ hướng dẫn các bạn viết nó thành dạng global component để có thẻ được gọi tại bất kỳ đâu trong ứng dụng của bạn. Chúng ta cùng bắt đầu nhé.
Để hiểu thêm về React Context API & Hooks các bạn tham khảo thêm tại đây:
Chúng ta bắt đầu bằng việc tạo một dự án mới sử dụng React nhé. Trong bài viết này mình sẽ sử dụng create-react-app cùng với yarn để tạo project mới. Các bạn xem hướng dẫn cài yarn tại đây nhé.
Tạo một project mới
Cài create-react-app package:
hieunv@HieuNV ~ % yarn global add create-react-app
yarn global v1.22.0
[1/4] ? Resolving packages...
[2/4] ? Fetching packages...
[3/4] ? Linking dependencies...
[4/4] ? Building fresh packages...
success Installed "[email protected]" with binaries:
- create-react-app
✨ Done in 14.08s.
Sau khi cài xong thì các bạn tạo project mới như sau:
yarn create react-app loading
Cấu trúc project được tạo bằng create-react-app
Tạo React context mới để lưu trạng thái của Loading component
Như các bạn nhìn thấy trong đoạn mã trên chúng ta đã sử dụng LoadingContext truyền trang thái loading và các hàm show và hide xuống các component con thông qua React Context.
Tại App component bạn đặt LoadingProvider là component cao nhất để tất các các component con bên trong có thể gọi các hàm show và hide khi cần thiết.
App.js
import React from 'react';
import { LoadingProvider } from './providers/LoadingProvider';
import { useLoading } from './context/loading.js';
import logo from './logo.svg';
import './App.css';
function App() {
const { show, hide } = useLoading();
return (
<LoadingProvider>
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">
Learn React
</a>
</header>
</div>
</LoadingProvider>
);
}
export default App;
Tại component bạn muốn gọi show hay hide loading thì chỉ cần thực hiện như sau:
import hàm useLoading() được khai báo trong context/loading.js
import { useLoading } from './context/loading.js';
Trong component bạn sử dụng hook useLoading để lấy các hàm show và hide:
const { show, hide } = useLoading();
Giờ chúng ta sửa lại một chút ở LoadingProvider để có thể show loading indicator
Các bạn để ý component Loading nhé. Chúng ta chỉ cần thêm một chút kỹ thuật css để cho component này đề lên trên tất cả các component khác bằng z-index đã có được loading indicator có thể được gọi ở bất kỳ đâu rồi.
Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết có thể giúp ích cho dự án của các bạn.
Bạn có thể hiểu Jetpack như 1 hệ sinh thái của android vậy. Jetpack là một tập hợp của Foundation, Architecture, Behavior, UI để giúp bạn tạo các ứng dụng Android tuyệt vời một cách nhanh chóng và dễ dàng.
Theo đánh giá cá nhân của mình thì đây sẽ là tương lai của Android. Khi nó đã tích hợp mọi thứ mà một lập trình viên Android cần. Và điều mình thích nhất ở Jetpack là bộ Architecture. Chưa hề có 1 chuẩn nào về architecture cho đến khi Google chính thức đưa ra 1 chuẩn architecture cho lập trình viên Android.
Các bạn có thể xem về Android Architecture Components tại đây
Trong bài viết này thì mình sẽ viết về WorkManager.
WorkManager
Define
WorkManager là 1 thành viên trong bộ Jetpack Architecture.
WorkManager manage your Android background jobs.
Tức là sao?. Mọi task vụ thực hiện dưới background như download, upload file, network…
Các bạn đều có thể dùng WorkManager để thực thi.
Overview
Schedule tasks with WorkManager
Là sao? Là thế này. Với WorkManager bạn có thể sắp xếp các task vụ của mình.
Bạn có thể quyết định khi nào thực hiện task. Và giả sử như bạn có nhiều task cần
thực hiện, bạn có thể quyết định thằng B chạy trước A rồi sau đó sẽ chạy thằng C hoặc ở
1 màn hình khác bạn lại cần chạy task theo thứ tự A > B > C. Nếu như task sau của bạn có input
là output của task trước thì bạn cũng đừng lo lắng. Với WorkManager bạn có thể làm được.
Chú ý nhé :
WorkManager dành cho các task vụ mà ngay cả khi bạn thoát ứng dụng thì task vẫn thực hiện
ví dụ như việc bạn upload data lên server. Còn các task vụ mà sẽ tắt khi thoát app ra
thì nên dùng ThreadPools nhé các bạn.
Xem qua cách hoạt động nào:
WorkManager chọn cách thích hợp để chạy tác vụ của bạn dựa trên các yếu tố như level API
thiết bị và trạng thái ứng dụng. Nếu WorkManager thực thi một trong các nhiệm vụ của bạn
trong khi ứng dụng đang chạy, WorkManager có thể chạy tác vụ của bạn trong một luồng mới.
Nếu ứng dụng của bạn không chạy, WorkManager chọn một cách thích hợp để schedule
một backgorund task – tùy thuộc vào mức API của thiết bị và các phụ thuộc kèm theo,
WorkManager có thể sử dụng JobScheduler, Firebase JobDispatcher hoặc AlarmManager.
Bạn không cần viết logic để tìm ra khả năng của thiết bị và chọn một API thích hợp,
thay vào đó bạn chỉ có thể giao nhiệm vụ của mình cho WorkManager và để cho nó chọn
tùy chọn tốt nhất. (Đoạn này hiểu đơn giản là bạn chỉ cần sử dụng WorkManager thôi, đừng
lăn tăn về việc nó sẽ sử dụng JobScheduler hay Firebase JobDispatcher, cũng như bạn k cần
viết code check API này kia làm gì. WorkManager đã xử lý điều đó).
WorkManager cung cấp một số tính năng nâng cao. Ví dụ, bạn có thể thiết lập một chuỗi các nhiệm vụ; khi một tác vụ kết thúc, WorkManager sẽ xếp hàng nhiệm vụ tiếp theo trong chuỗi. Bạn cũng có thể kiểm tra trạng thái của nhiệm vụ và các giá trị trả về của nó bằng cách quan sát LiveData của nó. (LiveData cũng là 1 component mới trong bộ Android Architecture Component nhé)
Classes and concepts
API WorkManager sử dụng một số lớp khác nhau. Tuy nhiên mình thấy có các class cần chú ý sau:
Worker: class xác định task cần thực hiện. API WorkManager bao gồm 1 class abstract Worker. Bạn cần extend class này và thực hiện công việc tại đây.
WorkRequest: đại diện cho một nhiệm vụ riêng lẻ. Ở mức tối thiểu, một đối tượng WorkRequest xác định lớp Worker nào sẽ thực hiện nhiệm vụ. Tuy nhiên, bạn cũng có thể thêm các chi tiết vào đối tượng WorkRequest, chỉ định những thứ như các trường hợp mà tác vụ sẽ chạy. Mỗi WorkRequest có một ID duy nhất được tạo tự động; bạn có thể sử dụng ID để thực hiện những việc như hủy một công việc xếp hàng đợi hoặc nhận trạng thái của tác vụ. WorkRequest là một lớp trừu tượng; trong mã của bạn, bạn sẽ sử dụng một trong các lớp con trực tiếp, OneTimeWorkRequest hoặc PeriodicWorkRequest.
WorkRequest.Builder: một lớp trợ giúp để tạo các đối tượng WorkRequest. Bạn sẽ sử dụng một trong các lớp con, OneTimeWorkRequest.Builder hoặc PeriodicWorkRequest.Builder.
Ràng buộc: chỉ định các hạn chế về thời điểm tác vụ sẽ chạy (ví dụ: “chỉ khi được kết nối với mạng”). Bạn tạo đối tượng Constraints với Constraints.Builder và chuyển các ràng buộc tới WorkRequest.Builder trước khi tạo WorkRequest.
WorkManager: enqueues và quản lý các yêu cầu công việc. Bạn chuyển đối tượng WorkRequest của bạn tới WorkManager để enqueue nhiệm vụ. WorkManager lên lịch nhiệm vụ theo cách như vậy để trải rộng tải trên tài nguyên hệ thống, tất nhiên nó sẽ thực hiện với các ràng buộc mà bạn đã chỉ định.
WorkStatus: chứa thông tin về một tác vụ cụ thể. WorkManager cung cấp một LiveData cho mỗi đối tượng WorkRequest. LiveData chứa đối tượng WorkStatus; bằng cách quan sát LiveData, bạn có thể xác định trạng thái hiện tại của tác vụ và nhận bất kỳ giá trị trả về nào sau khi tác vụ kết thúc.
Typical workflow
Giả sử bạn đang viết một ứng dụng thư viện ảnh và ứng dụng đó cần nén định kỳ hình ảnh được lưu trữ của nó. Bạn muốn sử dụng các API WorkManager để lên lịch nén ảnh. Trong trường hợp này, bạn không đặc biệt quan tâm khi nén xảy ra; bạn muốn thiết lập nhiệm vụ và quên nó đi.
Đầu tiên, bạn sẽ định nghĩa lớp Worker của mình và ghi đè phương thức doWork () của nó. Lớp Worker của bạn chỉ định cách thực hiện thao tác, nhưng không có bất kỳ thông tin nào về thời điểm tác vụ sẽ chạy.
public class CompressWorker extends Worker {
@Override
public Worker.WorkerResult doWork() {
// Thực hiện task ở đây.
//Trong case này task là nén ảnh và không có params
myCompress();
// Cho biết kết quả: Thành công hay thất bại
return WorkerResult.SUCCESS;
// trả về RETRY nếu thấy bại. Task sẽ được thực thi lại
// trả về FAILURE nếu muốn kết thúc, không thực thi lại.)
}
}
Tiếp theo, bạn tạo một đối tượng OneTimeWorkRequest dựa trên Worker đó, sau đó enqueue nhiệm vụ với WorkManager:
OneTimeWorkRequest compressionWork =
new OneTimeWorkRequest.Builder(CompressWorker.class)
.build();
WorkManager.getInstance().enqueue(compressionWork);
WorkManager chọn một thời điểm thích hợp để chạy tác vụ.Trong hầu hết các trường hợp, nếu bạn không chỉ định bất kỳ ràng buộc nào, WorkManager sẽ chạy tác vụ của bạn ngay lập tức. Nếu bạn cần kiểm tra trạng thái tác vụ, bạn có thể lấy đối tượng WorkStatus bằng cách xử lý LiveData. Ví dụ: nếu bạn muốn kiểm tra xem tác vụ đã hoàn tất chưa, bạn có thể sử dụng mã như sau:
WorkManager.getInstance().getStatusById(compressionWork.getId())
.observe(lifecycleOwner, workStatus -> {
// Do something with the status
if (workStatus != null && workStatus.getState().isFinished())
{ ... }
});
Nếu muốn, bạn có thể chỉ định các ràng buộc khi nhiệm vụ được chạy. Ví dụ: bạn có thể muốn chỉ định rằng tác vụ chỉ nên chạy khi thiết bị ở chế độ chờ và kết nối với nguồn. Trong trường hợp này, bạn cần tạo một đối tượng OneTimeWorkRequest.Builder và sử dụng trình tạo đó để tạo OneTimeWorkRequest:
// Tạo sự ràng buộc, khi nào thì task được thực thi
Constraints myConstraints = new Constraints.Builder()
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
// Có rất nhiều ràng buộc có sẵn.
// Tham khảo tại Constraints.Builder
.build();
// ...sau đó tạo một OneTimeWorkRequest sử dụng các ràng buộc đó
OneTimeWorkRequest compressionWork =
new OneTimeWorkRequest.Builder(CompressWorker.class)
.setConstraints(myConstraints)
.build();
Canceling a Task
Bạn có thể hủy một task sau khi bạn enqueue nó. Để hủy tác vụ, bạn cần ID task đó, mà bạn có thể nhận được từ đối tượng WorkRequest. Ví dụ, đoạn mã sau hủy bỏ yêu cầu compressionWork từ phần trước:
WorkManager cố gắng hết sức để hủy tác vụ, nhưng điều này vốn dĩ không chắc chắn – nhiệm vụ có thể đã chạy hoặc kết thúc khi bạn cố hủy nó. WorkManager cũng cung cấp các phương thức để hủy bỏ tất cả các nhiệm vụ trong một chuỗi công việc duy nhất, hoặc tất cả các nhiệm vụ với một thẻ (TAG) được chỉ định.
Advanced functionality (Nâng cao)
API WorkManager cung cấp các tính năng nâng cao cho phép bạn thiết lập các yêu cầu phức tạp.
Recurring tasks(Nhiệm vụ định kỳ)
Bạn có thể có một nhiệm vụ mà bạn cần phải thực hiện nhiều lần. Ví dụ: ứng dụng trình quản lý ảnh sẽ không muốn nén ảnh một lần. Nhiều khả năng, nó sẽ muốn kiểm tra hình ảnh được chia sẻ của nó thường xuyên như vậy, và xem nếu có bất kỳ hình ảnh mới hoặc thay đổi cần phải được nén. Tác vụ lặp lại này có thể nén hình ảnh mà nó tìm thấy, hoặc cách khác, nó có thể kích hoạt 1 tác vụ mới : “nén hình ảnh này”.
Để tạo một nhiệm vụ định kỳ, sử dụng lớp PeriodicWorkRequest.Builder để tạo một đối tượng PeriodicWorkRequest, sau đó enqueue PeriodicWorkRequest giống như cách bạn sẽ làm đối tượng OneTimeWorkRequest. Ví dụ, giả sử chúng ta định nghĩa một lớp PhotoCheckWorker để xác định các hình ảnh cần được nén. Nếu chúng ta muốn chạy tác vụ kiểm kê mỗi 12 giờ, chúng ta sẽ tạo một đối tượng PeriodicWorkRequest như sau:
new PeriodicWorkRequest.Builder photoWorkBuilder =
new PeriodicWorkRequest.Builder(PhotoCheckWorker.class, 12,
TimeUnit.HOURS);
// ...nếu bạn muốn, bạn có thể áp dụng các ràng buộc ở đây ...
PeriodicWorkRequest photoWork = photoWorkBuilder.build();
// enqueue task:
WorkManager.getInstance().enqueue(photoWork );
WorkManager cố gắng chạy nhiệm vụ của bạn tại khoảng thời gian bạn yêu cầu, tùy thuộc vào các ràng buộc mà bạn áp đặt và các yêu cầu khác của nó.
Chained tasks ( Chuỗi công việc)
Ứng dụng của bạn có thể cần chạy một số tác vụ theo một thứ tự cụ thể. WorkManager cho phép bạn tạo và enqueue một chuỗi công việc xác định nhiều nhiệm vụ và thứ tự chúng sẽ chạy.
Ví dụ: giả sử ứng dụng của bạn có ba đối tượng OneTimeWorkRequest: workA, workB và workC. Các nhiệm vụ phải được chạy theo thứ tự đó. Để enqueue chúng, tạo một chuỗi với phương thức WorkManager.beginWith (), truyền đối tượng OneTimeWorkRequest đầu tiên; phương thức đó trả về một đối tượng WorkContinuation, nó định nghĩa một chuỗi các nhiệm vụ. Sau đó, thêm các đối tượng OneTimeWorkRequest còn lại, theo thứ tự, với WorkContinuation.then (), và cuối cùng, enqueue toàn bộ chuỗi với WorkContinuation.enqueue ():
Hơi khó hiểu đúng không? Xem ví dụ nhé:
WorkManager.getInstance()
.beginWith(workA)
// Note: WorkManager.beginWith() trả về 1 đối tượng WorkContinuation
.then(workB) // FYI, then() cũng trả về 1 thể hiện của WorkContinuation
.then(workC)
.enqueue();
WorkManager chạy các tác vụ theo thứ tự được yêu cầu, theo các ràng buộc cụ thể của mỗi tác vụ. Nếu bất kỳ tác vụ nào trả về Worker.WorkerResult.FAILURE, toàn bộ chuỗi sẽ kết thúc.
Bạn cũng có thể truyền nhiều đối tượng OneTimeWorkRequest cho bất kỳ lệnh gọi startsWith () và .then () nào. Nếu bạn truyền một số đối tượng OneTimeWorkRequest cho một cuộc gọi phương thức duy nhất, WorkManager sẽ chạy tất cả các tác vụ đó (song song) trước khi nó chạy phần còn lại của chuỗi. Ví dụ:
WorkManager.getInstance()
// First, run all the A tasks (in parallel):
.beginWith(workA1, workA2, workA3)
// ...when all A tasks are finished, run the single B task:
.then(workB)
// ...then run the C tasks (in any order):
.then(workC1, workC2)
.enqueue();
Bạn có thể tạo các chuỗi phức tạp hơn bằng cách nối nhiều chuỗi với các phương thức WorkContinuation.combine (). Ví dụ: giả sử bạn muốn chạy một chuỗi như sau:
Để thiết lập trình tự này, hãy tạo hai chuỗi riêng biệt, sau đó ghép chúng lại với nhau thành một chuỗi thứ ba:
Trong trường hợp này, WorkManager chạy workA trước khi làm việc. Nó cũng hoạt động trước khi làm việc. Sau khi cả hai công việc và workD đã hoàn thành, WorkManager chạy workE.
Có một số biến thể của phương thức WorkContinuation cung cấp viết tắt cho các tình huống cụ thể. Ví dụ, có một phương thức WorkContinuation.combine (OneTimeWorkRequest, WorkContinuation…), hướng dẫn WorkManager hoàn thành tất cả các chuỗi WorkContinuation đã chỉ định, sau đó kết thúc với OneTimeWorkRequest được chỉ định. Để biết chi tiết, xem WorkContinuation nhé.
Unique work sequences (một chuỗi công việc duy nhất)
Bạn có thể tạo một chuỗi công việc duy nhất, bằng cách bắt đầu chuỗi với một cuộc gọi đến beginUniqueWork () thay vì beginWith (). Mỗi chuỗi công việc duy nhất có một tên; WorkManager chỉ cho phép một chuỗi công việc với tên đó tại một thời điểm. Khi bạn tạo một chuỗi công việc duy nhất mới, bạn chỉ định những gì WorkManager sẽ làm nếu có một chuỗi chưa hoàn thành có cùng tên:
Hủy chuỗi hiện tại và thay thế bằng trình tự mới
Giữ chuỗi hiện tại và bỏ qua yêu cầu mới của bạn
Nối chuỗi mới của bạn vào chuỗi hiện tại, chạy tác vụ đầu tiên của chuỗi mới sau khi tác vụ cuối cùng của chuỗi hiện tại kết thúc.
Một chuỗi công việc duy nhất có thể hữu ích nếu bạn có một nhiệm vụ không nên được enqueued nhiều lần. Ví dụ: nếu ứng dụng của bạn cần đồng bộ hóa dữ liệu với mạng, bạn có thể enqueue một chuỗi có tên là “sync” và chỉ định rằng tác vụ mới của bạn sẽ bị bỏ qua nếu đã có một chuỗi có tên đó. Một chuỗi công việc duy nhất cũng có thể hữu ích nếu bạn cần dần dần xây dựng một chuỗi nhiệm vụ dài. Ví dụ: ứng dụng chỉnh sửa ảnh có thể cho phép người dùng hoàn tác một chuỗi hành động dài. Mỗi hoạt động hoàn tác có thể mất một thời gian, nhưng chúng phải được thực hiện đúng thứ tự. Trong trường hợp này, ứng dụng có thể tạo chuỗi “hoàn tác” và nối thêm từng hoạt động hoàn tác vào chuỗi khi cần.
Tagged work ( Task được gắn thẻ)
Bạn có thể nhóm các nhiệm vụ của bạn một cách hợp lý bằng cách gán một chuỗi thẻ cho bất kỳ đối tượng WorkRequest nào. Để đặt thẻ, hãy gọi WorkRequest.Builder.addTag (), ví dụ:
OneTimeWorkRequest cacheCleanupTask =
new OneTimeWorkRequest.Builder(MyCacheCleanupWorker.class)
.setConstraints(myConstraints)
.addTag("cleanup")
.build();
Các lớp WorkManager cung cấp một số phương thức tiện ích cho phép bạn thao tác trên tất cả các nhiệm vụ với một thẻ cụ thể. Ví dụ, WorkManager.cancelAllWorkByTag () hủy bỏ tất cả các nhiệm vụ với một thẻ cụ thể, và WorkManager.getStatusesByTag () trả về một danh sách tất cả các WorkStatus cho tất cả các tác vụ với thẻ đó.
Input parameters and returned values
Để linh hoạt hơn, bạn có thể chuyển đối số cho công việc của mình và có nhiệm vụ trả về kết quả. Các giá trị được trả về và trả về là các cặp khóa-giá trị. Để chuyển một đối số cho một nhiệm vụ, hãy gọi phương thức WorkRequest.Builder.setInputData () trước khi bạn tạo đối tượng WorkRequest. Phương thức đó lấy một đối tượng Data, mà bạn tạo ra với Data.Builder. Lớp Worker có thể truy cập các đối số đó bằng cách gọi hàm Worker.getInputData (). Để xuất ra một giá trị trả về bạn sử dụng Worker.setOutputData (), lấy một đối tượng Data, bạn có thể lấy kết quả bằng cách quan sát LiveData .
Ví dụ, giả sử bạn có một lớp Worker thực hiện một phép tính tốn thời gian. Đoạn mã sau cho thấy lớp Worker sẽ trông như thế nào:
// Define the Worker class:
public class MathWorker extends Worker {
// Define the parameter keys:
public static final String KEY_X_ARG = "X";
public static final String KEY_Y_ARG = "Y";
public static final String KEY_Z_ARG = "Z";
// ...and the result key:
public static final String KEY_RESULT = "result";
@Override
public Worker.WorkerResult doWork() {
// Fetch the arguments (and specify default values):
int x = getInputData().getInt(KEY_X_ARG, 0);
int y = getInputData().getInt(KEY_Y_ARG, 0);
int z = getInputData().getInt(KEY_Z_ARG, 0);
// ...do the math...
int result = myCrazyMathFunction(x, y, z);
//...set the output, and we're done!
Data output = new Data.Builder()
.putInt(KEY_RESULT, result)
.build();
setOutputData(output);
return WorkerResult.SUCCESS;
}
}
Để tạo công việc và chuyển các đối số, bạn sẽ sử dụng mã như sau:
// Create the Data object:
Data myData = new Data.Builder()
// We need to pass three integers: X, Y, and Z
.putInt(KEY_X_ARG, 42)
.putInt(KEY_Y_ARG, 421)
.putInt(KEY_Z_ARG, 8675309)
// ... and build the actual Data object:
.build();
// ...then create and enqueue a OneTimeWorkRequest that uses those arguments
OneTimeWorkRequest.Builder argsWorkBuilder =
new OneTimeWorkRequest.Builder(MathWorker.class)
.setInputData(myData);
OneTimeWorkRequest mathWork = argsWorkBuilder.build();
WorkManager.getInstance().enqueue(mathWork);
Giá trị trả về sẽ có sẵn trong WorkStatus của tác vụ:
WorkManager.getInstance().getStatusById(mathWork.getId())
.observe(lifecycleOwner, status -> {
if (status != null) {
int myResult =
status.getOutputData().getInt(KEY_RESULT,
myDefaultValue));
// ... do something with the result ...
}
});
Nếu bạn có một chuỗi nhiệm vụ, kết quả đầu ra từ một nhiệm vụ có sẵn như là đầu vào cho nhiệm vụ tiếp theo trong chuỗi. Nếu đó là một chuỗi đơn giản, với một OneTimeWorkRequest duy nhất được theo sau bởi một OneTimeWorkRequest khác, tác vụ đầu tiên trả về kết quả của nó bằng cách gọi hàm setOutputData () và nhiệm vụ tiếp theo sẽ lấy kết quả đó bằng cách gọi phương thức getInputData (). Nếu chuỗi phức tạp hơn – ví dụ, bởi vì một số tác vụ gửi đầu ra tới một tác vụ duy nhất sau đây, bạn có thể định nghĩa một InputMerger trên OneTimeWorkRequest.Builder để xác định điều gì sẽ xảy ra nếu các tác vụ khác nhau trả về đầu ra có cùng khóa.
Lời kết
Đây là lần đầu tiên mình viết bài nên nếu có sai xót gì nhờ anh em đóng góp. Bài viết này mình tham khảo và dịch từ trang developer android, anh em có thể xem bản gốc tại đây
Nếu như bạn đã dành thời gian ra tìm hiểu về Architectute thì chắc chắn đã nghe qua “The Clean Architecture” rồi đúng không?
Việc định hình ra architecture cho một project chưa bao giờ là đơn giản, nó phụ thuộc vào rất nhiều yếu tố như yêu cầu của khách hàng, UI , kĩ năng của member, chức năng của app… Dựa vào các yếu tố đó mà Project Technical Lead hay TeamLead sẽ quyết định dùng architecture, mô hình nào vào project.
Trong bài viết này mình sẽ viết về The Clean Architecture – “Kiến trúc sạch”
Getting Started
Để viết ra một sản phẩm tốt là điều rất khó, tốt ở đây là sao? Ý kiến cá nhân của mình tốt có nghĩa là * easy to maintain * easy to debug * easy to develop * easy to understand * code clear
Sau một hồi tìm kiếm, mình đã tìm ra The Clean Architecture.
Bộ nguyên tắc của Clean Architecture như sau:
* Độc lập ( tách biệt) với Framework. * Dễ dàng cho việc test code. * Tách biệt giữa bussiness và UI * Tách biệt với cơ sở dữ liệu * Independent of any external agency
Nguyên tắc cuối mình để tiếng anh vì dịch ra tiếng việt nó tối nghĩa. khó hiểu. Nó được giải thích như sau: In fact your business rules simply don’t know anything at all about the outside world. Nghĩa là phần code logic của bạn nó là tách biệt, độc lập, nó không quan tâm đến cái cách mà nó được dùng như nào.
Tóm lại cái quan trọng nhất mình thấy ở architecture này chính là ĐỘC LẬP (TÁCH BIỆT). tách biệt mọi thứ, càng tách biệt rõ càng tốt, càng clear. Mà vì lẽ đó nên nếu apply architecture này thì số lượng class của bạn sẽ rất lớn đó nhé
Cấu trúc của The Clean Architecture
Mình sẽ giải thích về 4 vòng tròn thông qua 1 ứng dụng Movie nhé.
* Entities: Là các Object phục vụ cho Bussiness của bạn (là các model đó. Ví dụ MovieObject :P) * Use Cases: Mình thấy khá giống với khái niệm Usecase bên UML, nó đại diện cho các nghiệp vụ trong ứng dụng. ( Ví dụ Usecase: Show list HotMovies, show list favorite movies…) * Interface Adapters: hay còn gọi là tầng Presentation. Nó là cầu nối giữa tầng bussiness với tầng UI của bạn. Nếu bạn đã làm mô hình MVC hay MVP thì đây là nơi chứa Controller or Presenter. * Frameworks and Drivers: là tầng chứa UI, tools, frameworks, etc (ví dụ trong android thì tầng này là chứa Activity/Fragment đấy)
Android Architecture
Mục đích là tách ứng dụng thành các tầng tách biệt, không phụ thuộc vào nhau, như vậy mới dễ dàng cho việc test, cũng như maintain, phát triển.
Để đạt được điều này, chúng ta sẽ chia nhỏ dự án thành 3 lớp khác nhau, trong đó mỗi lớp có mục đích riêng và hoạt động riêng biệt với các mục đích khác nhau.
Mỗi lớp có thể thực hiện theo các mô hình, pattern khác nhau để đạt được mục đích của lớp đó.
Presentation Layer Đây là cầu nối giữa logic với UI (animation…) bạn có thể apply MVP hay MVC, MVVM tại đây, mình sẽ không đi chi tiết vào mô hình. Chú ý rằng tại tầng này chỉ có views (Fragment, Activity), sẽ không viết bất kì logic nào trừ logic liên quan đến UI.
Tầng Presenter sẽ sử dụng các interactors (uses case) để thực hiện logic dưới background thread (không thực hiện trên Mainthead(UI thread), sau đó trả về kết quả thông qua callback để view hiển thị.
Domain Layer
Tầng này chỉ viết logic mà thôi. Tại đây chúng ta sẽ định nghĩa các interactors (usecase, UserRepository).
chú ý rằng lớp này là 1 module thuần java (kotlin) không chứa bất kì phụ thuộc Android nào. Các thành phần bên ngoài khác muốn trỏ đến sẽ dùng interface.
Data Layer
Tất cả data của ứng dụng sẽ xử lý tại đây (hãy hiểu như này cho đơn giản, tầng này là tầng implement lại các interface repository ở tầng Domain cũng như định nghĩa ra các data model) .Chúng ta sẽ sử dụng Repository Pattern cho tầng này. Ví dụ khi chúng ta muốn lấy ra 1 bộ phim theo ID, repository sẽ quyết định lấy ra từ local nếu đã lưu bộ phim đó vào cache từ lần load trước, nếu chưa có sẽ request API để lấy bộ phim từ Server.
Lời kết
Như vậy là mình đã giới thiệu qua về The Clear Architecture.
Bài viết mang yếu tố chủ quan, đánh giá cá nhân nên nếu sai xót nhờ anh em bổ sung giúp mình nhé.
Thông thường khi viết ứng dụng node, chúng ta thương sử dụng các thử viện có sẵn trên npmjs. Trong bài viết này tôi sẽ hướng dẫn các bạn tạo một dự án node có thể chia nhỏ thành các packages nhưng vẫn có thể viết trên cùng một repository. Yarn cung cấp cho bạn chức năng để tạo và link các packages với nhau bằng yarn workspaces. Chúng ta tìm hiểu yarn workspaces hoạt động ra sao nhé
Tại thư mục package-a và package-b, thực hiện đăng ký package:
package-a
hieunv@HieuNV package-a % yarn link
yarn link v1.22.0
success Registered "package-a".
info You can now run `yarn link "package-a"` in the projects where you want to use this package and it will be used instead.
✨ Done in 0.04s.
package-b
hieunv@HieuNV package-b % yarn link
yarn link v1.22.0
success Registered "package-b".
info You can now run `yarn link "package-b"` in the projects where you want to use this package and it will be used instead.
✨ Done in 0.04s.
Thêm package-a và package-b như là dependency của package-c:
hieunv@HieuNV package-c % yarn link "package-a"
yarn link v1.22.0
success Using linked package for "package-a".
✨ Done in 0.04s.
hieunv@HieuNV package-c % yarn link "package-b"
yarn link v1.22.0
success Using linked package for "package-b".
✨ Done in 0.04s.
Sau khoảng thời gian làm việc trên MacOS, có quá nhiều lệnh bạn phải nhớ, hoặc đôi khi bạn phải thực hiện đi thực hiện lại nhiều lần, giá như terminal ngoài auto-complete mà có thể suggest được lệnh cho chúng ta thì tốt biết mấy. Bài viết này giúp bạn giải quyết điều đó!
Về mình, thực ra mình là một thằng Developer khá đơn giản nên mình thích mọi thứ cũng đơn giản, rộng rãi và thoáng đãng. Cũng bởi lẽ đó nên ngay từ khi bắt đầu sử dụng MacOS để làm việc, mình đã yêu thích Terminal mặc định của nó; ngay từ khi nhìn cái logo đơn giản nhưng không kém phần bắt mắt.
Để làm được việc suggest lệnh thì với /bin/bash là không đủ, chúng ta cần đổi bộ shell mặc định này sang một thằng khác mạnh mẽ hơn đó là Z-Shell hay còn gọi là zsh.
Đi kèm zsh có một framework đó là oh-my-zsh support mọi thứ từ theme, command line prompts, auto suggestions, .etc. Trong bài mình sẽ hướng dẫn mọi người cài đặt zsh và oh-my-zsh trên MacOS, tích hợp plugin zsh-autosuggestions cho oh-my-zsh để terminal có thể tự động suggest lệnh cho chúng ta. Hãy cùng bắt đầu nhé!
Cài đặt zsh
Cài đặt trực tiếp zsh thông qua brew như sau:
$ brew install zsh
Kiểm tra xem đã cài đặt thành công chưa:
$ which zsh
/usr/local/bin/zsh
Cài đặt oh-my-zsh
Bây giờ chúng ta cài đặt oh-my-zsh, là một framework cho zsh sẽ giúp mình cài đặt nhiều thứ như theme, PS1 prompts:
$ sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
Cài đặt zsh-autosuggestions
Cài đặt plugin zsh-autosuggestions, giúp tự động suggetions các lệnh mà mình đã dùng:
➜ brew install zsh-autosuggestions
$ brew install zsh-autosuggestions
Tiếp đó để kích hoạt plugin zsh-autosuggtestions lên, mình cần chạy thêm lệnh sau:
Maven dùng pom để quản lý packages, Node thì có packages.json. Anh em python thì quản lý python packages bằng pip (Package installer for Python). Tuy nhiên khi sử dụng pip sẽ gặp phải tình huống các dự án khác nhau sử dụng dánh sách packages khác nhau. Vấn đề lớn hơn nữa có thể xảy ra tình huống 2 dự án nào đó sử dụng cùng package ở hai phiên bản khách nhau. Trong bài viết này tôi sẽ hướng dẫn các bạn cách quản lý python packages cho các dự án khác nhau mà không bị phụ thuộc vào nhau. Chúng ta cùng bắt đầu nhé.
Với python các packages không được cài đặt cục bộ giống như node. Do đó chúng ta cần tạo ra các môi trường khác nhau với các packages khác nhau để sử dụng cho các dự án khác nhau.
Cài đặt virtualenv
pip install virtualenv
Cài đặt virtualenvwrapper
pip install virtualenvwrapper
Activate virtualenv mỗi khi bật khởi động Terminal
Các bạn thêm đoạn sau vào ~/.zshrc để virutalenv có thể dượcd khởi động mỗi khi bạn bật Terminal
Vào tháng 8 năm 2014, Google công bố sử dụng HTTPS để khắc phục những vẫn đề bảo mật mà phương thức HTTP đang gặp phải.
Đối với hầu hết các công ty, việc Google khuyến cáo sử dụng HTTPS là lý do để thực hiện chuyển đổi sang giao thức đó, nhưng bản thân mỗi chúng ta cũng cần hiểu sự khác biệt giữa chúng – ưu và nhược điểm của HTTP và HTTPS. Tôi sẽ bắt đầu bằng việc giới thiệu tổng quan về giao thức HTTP và sau đó xem xét lý do tại sao Google muốn các trang web chuyển đổi sang sử dụng HTTPS.
HTTP là gì, hoạt động như thế nào, và tại sao nó không được bảo mật?
HTTP – HyperText Transfer Protocol, là một giao thức đã tồn tại hơn 15 năm, dùng để truyền tải thông tin qua Internet.
Cũng như nhiều giao thức khác, HTTP hoạt động theo mô hình Client – Server. Trình duyệt web tạo request được gọi là Client, nơi nhận và phản hồi request đó là Server.
Giả sử, bạn đang ngồi trong quán cafe và thử đăng nhập Facebook thông qua wifi của quán (giả định là Facebook đang dùng HTTP). Mạng wifi của quán là public, bất cứ ai kết nối với nó đều có thể truy cập dữ liệu đang được chuyển giao.
Bây giờ chúng ta hãy xem những gì đang xảy ra với dữ liệu của bạn nếu như website sử dụng HTTP.
Dữ liệu ở đây là bao gồm tất cả thông tin đăng nhập, mật khẩu, … cho tài khoản Facebook của bạn.
Để đăng nhập vào Facebook, bạn cần nhập các thông tin như email, số điện thoại và mật khẩu. Ngay khi bạn nhấp vào nút đăng nhập, dữ liệu của bạn sẽ được gửi đến Server của Facebook. Server nhận dữ liệu, và xác thực nó. Nếu thông tin nhập là chính xác, Server sẽ gửi về HTTP status là “OK”, và bạn được đăng nhập vào tài khoản của mình. Mọi thứ có vẻ ez.
Nhưng vấn để xảy ra ở đây là, nếu dữ liệu của bạn gửi lên server thông qua HTTP, thì nó sẽ không được mã hóa (HTTP không mã hóa dữ liệu) và vì vậy, bất kỳ dữ liệu nào được truyền thông qua giao thức HTTP đều có thể bị đánh cắp hoặc thay đổi từ bên thứ ba.
Có thể bạn chưa bao giờ nghe tới Network sniffing attack, nhưng loại tấn công đó khá phổ biến.
Sniffing attacks là một loại tấn công mà các Hacker sử dụng để lấy cắp thông tin “nhạy cảm” của bạn (vd: password, creadit card, users id, …).
Để thực hiện việc này, các hacker thường sử dụng Sniffer – một chương trình có thể bắt được các gói tin truyền qua mạng.
Sniffer là công cụ để phân tích và khắc phục các sự cố mạng thông qua việc bắt các gói tin, nhưng Hacker có thể lợi dụng điều đó để sử dụng chúng vào mục đích bất chính.
Nếu các gói tin không được mã hóa, dữ liệu trong các gói tin này nó thể được lấy bởi một Sniffer. Sniffer sẽ phân tích và đọc nội dung gói tin, từ đó, các hacker đã có thể lấy được các thông tin private của bạn một cách dễ dàng.
Như chúng ta đã thấy, HTTP có một điểm yếu chí cmn mạng – thông tin chuyển tới Server thông qua HTTP không được mã hóa. Điều đó, về mặt lý thuyết, có thể bị chặn bởi Hacker bất cứ lúc nào.
Đối với những trang web thuần túy để đọc báo hay xem thông tin thì không có vấn đề gì lớn. Nhưng nó rất nguy hiểm khi bạn thực hiện các giao dịch trực tuyến, mà trong đó phải cung cấp các thông tin cá nhân quan trọng như giao dịch ngân hàng, mua sắm, …
Tuy nhiên, điểm yếu đó của HTTP có thể được giải quyết dễ dàng bằng cách sử dụng 1 protocol khác – HTTPS.
HTTPS ngoài Sniffing attacks ra cũng có thể bảo vệ bạn khỏi những kiểu tấn công khác như man-in-the-middle attacks, DNS rebinding, replay attacks. Nhưng trong bài viết này, tôi chỉ để cập tới cách để HTTPS bảo vệ bạn khỏi Sniffing attacks đã nói ở trên thôi.
HTTPS là gì và làm thế nào nó có thể bảo vệ website của bạn
Giống như HTTP, HTTPS cũng là một giao thức giúp truyền thông tin giữa Client và Server. (Nó là một phiên bản của HTTP với thêm chứ “S” ở cuối – viết tắt của “Secure”). HTTPS bảo mật dữ liệu của bạn bằng cách sử dụng giao thức TSL (Transport Layer Security) hay còn gọi là SSL. Nhưng SSL là gì?
SSL là tiêu chuẩn bảo mật cung cấp 3 lớp bảo vệ:
Mã hóa (Encryption): tất cả dữ liệu được gửi giữa browsers (Client) và Server đều được mã hóa. Nếu Hacker lấy được gói tin đó, cũng không thể giải mã được.
Toàn vẹn dữ liệu (Data integrity): Đảm bảo dữ liệu truyền đi không thể sửa đổi hoặc bị hỏng mà không bị phát hiện.
Xác thực (Authentication): Xác minh xem bạn thực sự đang giao tiếp với Server đã định hay không.
Để ý một chút, khi truy cập website sử dụng HTTPS, trên url của bạn sẽ hiển thị ra chữ màu xanh như sau:
Hoạt động của SSL
SL sử dụng cái gọi là Public Key Cryptography hoặc hệ thống Public Key Infrastructure (PKI). Hệ thống PKI (key không đối xứng) sử dụng 2 key khác nhau để mã hóa thông tin: public key và private key. Bất cứ thứ gì được mã hóa bằng public key đều chỉ có thể giải mã bằng private key tương ứng và ngược lại.
Lưu ý rằng, private key – giống như cái tên của nó, nên được bảo vệ kỹ và chỉ được truy cập bởi chính owner mà thôi. Với một trang web, private key phải được giữ an toàn trên Server. Nhưng ngược lại, public key lại được cấp phát công khai cho bất kỳ ai, và tất cả mọi người đều cần nó để giải mã thông tin đã được mã hóa trước đấy bằng private key. Bây giờ, chúng ta đã hiểu cách làm việc của cặp public key và private key, ta sẽ tiếp tục mô tả quá trình hoạt động SSL thông qua từng bước như sau.
Giả sử bạn truy cập 1 website có sử dụng HTTPS:
Bước 1: Thiết lập một “giao tiếp” an toàn giữa Server và Client – còn được gọi là Handshake (bắt tay). Quá trình Handshake được bắt đầu khi browsers truy cập trang web thông qua url. Bằng cách request trên, Client sẽ khởi tạo kết nối SSL với Server cùng với thông tin về phiên bản và kiểu mã hóa. Việc Client gửi request và khởi tạo kết nối SSL được gọi là client hello.
Bước 2: Bước tiếp theo được gọi là server hello. Sau khi nhận được yêu cầu từ Client, Server trả về cho Client SSL certificate cùng với public key của nó. Hoàn tất quá trình chào hỏi xã giao.
Bước 3: Client nhận được dữ liệu từ Server, browser sẽ xác minh SSL certificate đó. Những certificate này được kiểm soát bởi các tổ chức bảo mật (Certificate Authority) như Symantec, Comodo, GoDaddy. SSL Certificate là một khối dữ liệu bao gồm nhiều thông tin về server như:
Tên domain.
Tên công ty sở hữu.
Thời gian certificate được cấp.
Thời hạn certificate.
Public key.
Bước 4: Sau khi xác minh xong, Browser sẽ sinh ra 1 Key - K và được mã hóa bởi public key nhận được từ bước 2. K sẽ được sử dụng để mã hóa tất cả dữ liệu truyền tải giữa Client và Server.
Bước 5: Do quá trình mã hóa dữ liệu sử dụng PKI (key đối xứng), nên Client cần gửi cho Server cái khóa K này để giải mã gói tin. Server sẽ dùng private key để giải mã gói tin này và lấy được thông tin về khóa K.
Bước 6: Từ đây, các thông tin truyền tải giữa Client và Server đều được mã hóa bằng khóa K. Khóa K này là unique và chỉ có hiệu lực trong thời gian Session đó tồn tại.
Việc sử dụng HTTPS sẽ bảo vệ trang web của bạn tới những kiểu tấn công mà tôi đã đề cập trước đó. Bạn có authentication, bạn có thể biết rằng mình đang giao tiếp một cách an toàn với Server dự định. Dữ liệu của bạn được mã hóa encryption – ngay cả khi sniffer lấy được gói tin, cũng không thể giải mã được nội dung bên trong. Và tất nhiên bạn có được toàn vẹn dữ liệu data integrity, vì vậy bạn có thể truyền những dữ liệu nhạy cảm mà không cần lo lắng về việc nó bị hỏng hoặc sửa đổi mà không phát hiện ra.
Bạn có nên sử dụng HTTPS
Trước khi đưa ra quyết định sử dụng HTTPS thay cho HTTP, tôi sẽ tổng hợp những lợi ích chính nếu website của bạn sử dụng HTTPS:
Security: Tất cả thông tin truyền tải giữa Client và Server đều được mã hóa và xác minh. Điều này giúp bạn chống được một số kiểu tấn công của hacker như man-in-the-middle attacks, DNS rebinding, replay attacks.
Trust: Người dùng sẽ cảm thấy an tâm với trang web của bạn hơn. HTTPS giúp xây dựng lòng tin với họ, nhất là khi web của bạn cung cấp các dịch vụ dính đến tiền (yaoming).
SEO: Việc sử dụng HTTPS sẽ tăng thứ hạng tìm kiếm trang web của bạn trên các search engine.