Chương 9: Bộ nhớ cache (The Memory Cache) Lưu trữ dữ liệu lâu bền đòi hỏi một phương tiện lưu trữ mà vẫn giữ được dữ liệu thông qua tổn thất điện năng và hệ thống khởi động lại. Hôm nay vừa được lựa chọn là các ổ đĩa cứng, một thiết bị lưu trữ bao gồm đĩa tròn phủ vật liệu từ tính mà trên đó các dữ liệu được mã hóa. Các đĩa cứng quay với một tốc độ không đổi trong khi một bộ cảm biến chuyển động dọc theo bán kính, đọc và viết bit trên đĩa cứng khi chúng đi qua. Đọc hoặc viết một mảnh dữ liệu cụ thể đòi hỏi một đĩa tìm kiếm để xác định vị trí các cảm biến ở bán kính thích hợp và chờ cho đĩa xoay cho đến khi dữ liệu mong muốn là bên dưới. Ổ đĩa cứng nhanh cách đáng kinh ngạc, tất cả thứ được xem xét, nhưng đối với các ứng dụng web, tìm kiếm đĩa có thể tốn kém. Lấy một thực thể từ các kho dữ liệu bằng cách quan trọng có thể mất thời gian vào thứ tự của hàng chục mili giây. Hầu hết các ứng dụng web hiệu suất cao sử dụng một bộ nhớ cache. Một bộ nhớ cache bộ nhớ sử dụng một phương tiện lưu trữ ổn định, thường là bộ nhớ RAM của máy cho cache rất nhanh chóng đọc và viết truy cập vào các giá trị. Một bộ nhớ cache phân phối cung cấp khả năng mở rộng, lưu trữ tạm thời phù hợp cho các hệ thống phân phối, vì vậy nhiều quá trình có thể truy cập cùng một dữ liệu. Vì bộ nhớ là dễ bay hơi, nó sẽ bị xóa trong một utage, cache là không hữu ích để lưu trữ lâu dài, hoặc thậm chí lưu trữ chính ngắn hạn cho các dữ liệu quan trọng. Nhưng đó là tuyệt vời như là một hệ thống thứ cấp cho truy cập dữ liệu nhanh cũng giữ ở những nơi khác, chẳng hạn như kho dữ liệu. Nó cũng đủ như là bộ nhớ tốc độ cao trên toàn cầu đối với một số mục đích sử dụng. App Engine dịch vụ phân phối bộ nhớ cache, được biết đến như memcache trong danh dự của các hệ thống memcached ban đầu mà nó giống, cửa hàng cặp khóa-giá trị. Bạn có thể thiết lập một giá trị với một khóa, và nhận được các giá trị đã cho khóa. Một giá trị có thể lên đến một megabyte trong kích thước. Một khóa lên đến 250 byte, và các API chấp nhận các phím lớn hơn và sử dụng một thuật toán băm để chuyển chúng đến 250 byte.
Thiết lập một giá trị duy nhất trong bộ nhớ cache là nguyên tử: khóa hoặc được các giá trị mới hoặc giữ lại một tuổi (hoặc vẫn bỏ thiết lập). Các memcache không hỗ trợ các giao dịch như kho dữ liệu không. Nếu bạn nhận được một giá trị, sau đó cố gắng thiết lập một
giá trị dựa trên những gì bạn có, giá trị đầu tiên có thể đã thay đổi kể từ đó là cường điệu. App Engine memcache bao gồm khả năng tăng và giảm giá trị giá trị số như là một hoạt động nguyên tử. Một cách phổ biến để sử dụng memcache với kho dữ liệu là để cache các tổ chức kho dữ liệu bằng khoá của chúng. Khi bạn muốn lấy một thực thể theo khóa, đầu tiên bạn kiểm tra memcache cho một giá trị với khóa đó, và sử dụng nó nếu tìm thấy (được biết đến như một lần truy cập cache). Nếu nó không phải trong memcache (cache), bạn lấy nó từ kho dữ liệu, sau đó đặt nó trong memcache thực hiện như vậy để truy cập sẽ tìm thấy chúng ở đó. Với chi phí của một số lượng nhỏ của chi phí trong quá trình đầu tiên tìm nạp, lộ trình kế tiếp trở nên nhanh hơn nhiều. Nếu thực thể thay đổi trong các kho dữ liệu, bạn có thể cố gắng để cập nhật các memcache khi thực thể được cập nhật trong kho dữ liệu, vì vậy yêu cầu tiếp theo có thể tiếp tục đi vào bộ nhớ cache nhưng xem dữ liệu mới. Điều này chủ yếu là các công trình, nhưng nó có hai vấn đề nhỏ. Đối với một, có thể là bản cập nhật memcache sẽ thất bại ngay cả khi cập nhật kho dữ liệu thành công, để lại dữ liệu cũ trong bộ nhớ cache. Ngoài ra, nếu hai quá trình cập nhật các thực thể kho dữ liệu tương tự, thì sau đó cập nhật các memcache, kho dữ liệu sẽ có dữ liệu chính xác (nhờ các trao đổi kho dữ liệu), nhưng cập nhật memcache sẽ có giá trị của bất cứ bản cập nhật sau cùng. Vì khả năng này, nó tốt hơn là xóa các khóa memcache khi thay đổi kho dữ liệu, và để cho các lần tiếp đọc điền bộ nhớ cache với một giá trị hiện tại. Đương nhiên, việc xóa cũng có thể thất bại. Vì không có cách nào để cập nhật cả hai kho dữ liệu và memcache trong một duy nhất trao đổi, không có cách nào để tránh khả năng rằng bộ nhớ cache có thể chứa dữ liệu cũ. Để giảm thiểu tối đa thời gian mà các memcache sẽ có một giá trị cũ, bạn có thể cung cấp cho các giá trị thời gian hết hạn khi bạn thiết lập nó. Khi thời gian hết hạn trôi qua, bộ
nhớ cache unsets khóa, và tiếp theo là một kết quả đọc trong một cache và gây ra một tươi lấy từ kho dữ liệu. Tất nhiên, mô hình bộ nhớ đệm này làm việc cho hơn thực thể chỉ kho dữ liệu. Bạn có thể sử dụng nó cho các truy vấn kho dữ liệu, các cuộc gọi dịch vụ web được thực hiện với
URL Fetch, tính toán tốn kém, hoặc bất kỳ dữ liệu khác có thể được thay thế bằng một hoạt động chậm, nơi mà lợi ích của việc truy cập nhanh vượt khả năng của staleness. Điều này là rất thường xảy ra với các ứng dụng web mà cách tốt nhất là để cache linh hoạt. Xem xét thông qua ứng dụng của bạn cho cơ hội để làm cho sự cân bằng này, và thực hiện bộ nhớ đệm khi cùng giá trị là cần thiết một số tùy ý lần, đặc biệt là nếu số đó tăng với giao thông. nội dung trang web như một bài viết trên một trang web tin tức thường rơi vào thể loại này. Caching tăng tốc độ yêu cầu và tiết kiệm thời gian CPU, và làm giảm khả năng của kho dữ liệu đọc thất bại ảnh hưởng đến ứng dụng của bạn. Các API cho dịch vụ memcached là đơn giản. Chúng ta sẽ nhìn vào giao diện Python đầu tiên, sau đó giao diện Java, với trọng tâm về bộ nhớ đệm đơn vị kho dữ liệu. Các Java Memcached API (The Java Memcache API) Cũng như các dịch vụ khác, App Engine bao gồm hai giao diện Java với dịch vụ memcache. Một là một giao diện độc quyền cung cấp truy cập trực tiếp đến các tính năng của dịch vụ. Mặt khác là một thực hiện JCache, một tiêu chuẩn giao diện của JSR 107 đề xuất. Theo bài viết này, JSR 107 không phải là một tiêu chuẩn được phê duyệt, và cách khuyến khích để sử dụng nó trên App Engine là thay đổi liên tục. Vì vậy, bây giờ, chúng ta sẽ chỉ xem xét các giao diện độc quyền. Để biết thêm thông tin về cách sử dụng giao diện JCache với App Engine, tham khảo tài liệu App Engine. Các dịch vụ Java memcache API cho phép sử dụng của bất kỳ đối tượng serializable như là khóa hoặc giá trị của một cặp khóa-giá trị được lưu trữ trong bộ nhớ cache. Phím mà tuần tự dạng là lớn hơn 250 byte được băm đến 250 byte. Các giá trị có thể lên đến một megabyte trong kích thước.
Trong nhiều trường hợp, bạn có thể làm cho một lớp dữ liệu JPA serializable chỉ đơn giản bằng cách khai báo rằng các lớp thực hiện Serializable, không có những thay đổi khác. Nếu bạn đang sử dụng các API kho dữ liệu cấp thấp, các lớp thực thể và tất cả các lớp giá trị thuộc tính thực thi giao diện Serializable Với dịch vụ App Engine memcache Java API, bạn tương tác với các dịch vụ sử dụng một
đối
tượng
MemcacheService,
mà
bạn
nhận
được
từ
MemcacheServiceFactory.getMemcacheService().Các thao tác cơ bản có sẵn như là phương thức: MemcacheService: put(), get(), and delete(). import com.google.appengine.api.memcache.MemcacheService; import com.google.appengine.api.memcache.MemcacheServiceFactory; // ... List<String> headlines; MemcacheService memcache = MemcacheServiceFactory.getMemcacheService(); memcache.put("headlines", headlines); headlines = (List<String>) memcache.get("headlines"); memcache.delete("headlines"); Phương thức put () nhận khóa và giá trị, một trong đó có thể là đối tượng serializable. Được gọi là chỉ với hai đối số này, giá trị mới được lưu trữ không có thời gian hết hạn, và cả hai sẽ tạo ra một giá trị mới với các phím được hoặc thay thế sẵn có, nếu có. Để thiết lập một giá trị với thời gian hết hạn, bạn put() một đối tượng Expiration như là
đối số thứ ba. Bạn tạo ra giá trị này với một phương thức tĩnh trong lớp Expiration. import com.google.appengine.api.memcache.Expiration; // ... memcache.put("headlines", headlines,Expiration.byDeltaSeconds(300));
Bạn có thể sử dụng các phương pháp thức thay thức byDeltaMillis(int) cho thời gian hết hạn trong milli-giây, hoặc onDate(java.util.Date) cho thời gian hết hạn vào một thời điểm tuyệt đối trong tương lai. Để thay đổi cách put () với giá trị hiện tại với các khóa, cung cấp một giá trị MemcacheService.SetPolicy liệt kê như là đối số thứ tư. Bạn có thể sử dụng null cho đối số thứ ba nếu bạn không muốn thiết lập thời hạn. import com.google.appengine.api.memcache.MemcacheService.SetPolicy; // ... memcache.put("headlines", headlines, null, SetPolicy.ADD_ONLY_IF_NOT_PRESENT); Ba
lựa
chọn
thiết
lập
chính
sách
là
SET_ALWAYS
(mặc
định),
ADD_ONLY_IF_NOT_PRESENT (chỉ tạo ra, không ghi đè lên), và REPLACE CHỈ NẾU HIỆN TẠI (chỉ ghi đè lên, không tạo ra). Bạn có thể kiểm tra xem bộ nhớ cache contains một giá trị với một khóa đưa ra mà không lấy giá trị bằng cách sử dụng Phương thức contains (). Phương thức này có một khóa như là đối số của nó, và trả về đúng hay sai boolean headlinesAreCached = memcache.contains("headlines"); Để xóa một giá trị cho một khóa nào đó, bạn gọi phương pháp delete () với khóa như là đối số của nó. Bạn có thể chỉ định một số lượng thời gian mà các quá trình không được phép readd khóa, như một số nguyên dài mili giây trong đối số thứ hai. Điều này rất hữu ích cho việc đảm bảo rằng nhiều quy trình người làm việc đồng thời làm việc với cùng một dữ liệu không vô tình lùi lại bộ nhớ cache xóa với một bản cập nhật memcache.delete("tempnode91512", 5); API cấp thấp bao gồm các phương thức để thiết lập, lấy, hoặc xóa nhiều mục trong một lời gọi đợt duy nhất. Điều này là nhanh hơn so với làm một cuộc gọi dịch vụ cho mỗi
mục. Tuy nhiên, tổng kích thước của các lời gọi và phản hồi của nó được giới hạn trong một megabyte. Phương thức putAll () lưu nhiều giá trị trong một lô. Nó chấp nhận một Map Object> các khóa và giá trị như là đối số đầu tiên của nó. Nó thể chấp nhận Expiration và giá trị SetPolicy, như đối tác của đơn hàng của mình. import java.util.HashMap;
import java.util.Map; // ... Map<Object, Object> articleSummaries = new HashMap<Object, Object>(); articleSummaries.put("article00174", "..."); articleSummaries.put("article05234", "..."); articleSummaries.put("article15820", "..."); memcache.putAll(articleSummaries); Phương thức GetAll () trả về nhiều mục trong một lô. Nó chấp nhận một Collection <Object> các khóa như là đối số của nó, và trả về một Map <Object, Object> các khóa và giá trị cho tất cả các khóa được cung cấp đã được tìm thấy trong bộ nhớ cache. import java.util.List; // ... List<Object> articleSummaryKeys = Arrays.<Object>asList( "article00174", "article05234", "article15820"); Map<Object, Object> articleSummaries = memcache.getAll(articleSummaryKeys); Phương thức deleteAll () xóa nhiều mục trong một lô. Nó chấp nhận một Collection các khóa như là đối số của nó. Nó cũng thể chấp nhận một thời gian khóa đọc trong mili giây như là đối số thứ hai của mình. memcache.deleteAll(articleSummaryKeys);
Bất kỳ giá trị memcached có thể được lưu trữ trong một không gian tên. Không gian tên cho phép bạn phân đoạn không gian chính của các loại mà bạn đang lưu trữ, do đó bạn không phải lo lắng về xung đột chính giữa sử dụng khác nhau của bộ nhớ cache. Để sử dụng không gian tên trong cấp thấp Java API, bạn thiết lập các không gian tên trên MemcacheService bằng cách gọi phương thức của nó setNamespace (). Tất cả các hoạt động tiếp theo sử dụng không gian tên được đưa ra khi thao tác các giá trị. memcache.setNamespace("News");
memcache.put("headlines", headlines); memcache.setNamespace("User"); memcache.put("headlines", userHeadlines); // Get User:"headlines". userHeadlines = (List<String>) memcache.get("headlines"); / Get News:"headlines". memcache.setNamespace("News"); headlines = (List<String>) memcache.get("headlines"); Nếu một giá trị memcache là một số nguyên (int hay long), bạn có thể tăng hoặc giảm số lượng các nguyên tử-có nghĩa là, không can thiệp với các quá trình khác làm điều tương tự bằng cách sử dụng Phương thức increment (). Phương pháp này mất khóa và số tiền thặng dư (mà có thể làm xấu đi). Nó trả về các giá trị mới (Long), hoặc null nếu không có giá trị đã được lưu trữ với khóa. memcache.put("work_done", 0); // ... Long workDone = memcache.increment("work_done", 1); Các Java API cấp thấp cung cấp quyền truy cập vào giá trị hơn API JCache. Phương thức getStatistics () trả về một đ ối tượng thống kê số liệu, với một phương thức cho từng giá trị có sẵn. import com.google.appengine.api.memcache.Stats; // ...
Stats stats = memcache.getStatistics(); int ageOfOldestItemMillis = stats.getMaxTimeWithoutAccess(); Giá trị sẵn bao gồm: getHitCount() Đếm số lượng truy cập bộ nhớ cache. getMissCount() Đếm số lượng bộ nhớ cache.
getItemCount() Số lượng các mục hiện có trong bộ nhớ cache. getTotalItemBytes() Tổng kích thước của mục hiện có trong bộ nhớ cache. getBytesReturnedForHits() Tổng số byte trả về để đáp ứng với số truy cập bộ nhớ cache, bao gồm các khóa và các giá trị. getMaxTimeWithoutAccess() Thời gian của các mục gần đây nhất truy cập vào bộ nhớ cache, chỉ trong vài giây. Không có cách nào để thiết lập một cách rõ ràng các giá trị memcache. Chúng được tích lũy qua thời gian hoạt động của dịch vụ, và do đó chủ yếu là hữu ích cho các so sánh tương đối trong thời gian ngắn ngủi của thời gian. Theo mặc định, nếu dịch vụ memcache là không có hoặc có một lỗi khi truy cập các dịch vụ, các cấp thấp API và API vận hành JCache như khóa không tồn tại. Cố gắng để đưa các giá trị mới không âm thầm; các phương thức bốn đối số put () sẽ trả về false trong trường hợp này, nếu như put thất bại do các chính sách đề ra. Những nỗ lực để có được giá trị này sẽ hoạt động như bộ nhớ cache. Ở cấp thấp Java API, bạn có thể thay đổi hoạt động này bằng cách cài đặt một trình xử lý lỗi thay thế. Phương thức setErrorHandler () của MemcacheService mất một đối tượng mà thực hiện các giao diện ErrorHandler. Hai triển khai như vậy được cung cấp: LogAndContinueErrorHandler
và
StrictErrorHandler.
Mặc
định
là
LogAndContinueErrorHandler với mức độ ghi của nó thiết lập để FINE (các "gỡ rối" cấp
Administration Console). StrictErrorHandler ném MemcacheServiceException cho tất cả các lỗi dịch vụ tạm thời. import com.google.appengine.api.memcache.StrictErrorHandler; // ... memcache.setErrorHandler(new StrictErrorHandler()); Xử lý lỗi có thể có đáp lại tùy chỉnh cho các giá trị không hợp lệ và lỗi dịch vụ. Các loại ngoại lệ ném ra bởi vận hành API như bình thường.
Chương 10: Tìm nạp URL và Tài nguyên Web (Fetching URLs in Java) Một ứng dụng App Engine có thể kết nối đến các trang web khác trên Internet để lấy dữ liệu và giao tiếp với các dịch vụ web. Nó làm điều này không phải bằng cách mở một kết nối đến máy chủ từ xa từ máy chủ ứng dụng, nhưng thông qua một dịch vụ mở rộng được gọi là dịch vụ URL Fetch. Nó có thể chịu đựng việc duy trì các kết nối từ các máy chủ ứng dụng, và đảm bảo rằng tìm nạp tài nguyên thực hiện tốt bất kể như thế nào bất cứ xử lý yêu cầu được Tìm kiếm nguồn cùng một lúc. Cũng như với các bộ phận khác của cơ sở hạ tầng App Engine, URL Fetch dịch vụ được sử dụng bởi các ứng dụng khác của Google để nạp các trang web. Dịch vụ hỗ trợ Tìm kiếm URL bằng cách sử dụng giao thức HTTP, và sử dụng HTTP với SSL (HTTPS). Các phương pháp khác đôi khi kết hợp với URL (như FTP) không được hỗ trợ. Lưu ý rằng với các kết nối HTTPS, các dịch vụ không thể xác thực điểm đến của các kết nối, vì không có chuỗi chứng chỉ tin tưởng. Dịch vụ chấp nhận tất cả các chứng chỉ. Điều này có nghĩa các giao thức kết nối tự nó không thể bảo vệ chống lại "man-inthe-middle" tấn công, mặc dù giao thông vẫn được mã hóa. Vì URL Fetch dịch vụ dựa trên cơ sở hạ tầng của Google, các dịch vụ thừa kế một vài hạn chế đã được đưa ra trong các thiết kế ban đầu của các proxy HTTP cơ bản. Dịch vụ hỗ trợ trong năm hành động HTTP thông thường nhất GET, POST, PUT, HEAD, và
DELETE) nhưng không cho phép người khác hoặc sử dụng một hành động không chuẩn.
Ngoài ra, nó chỉ có thể kết nối với các cổng TCP chuẩn cho từng phương pháp (80 cho HTTP, và 443 cho HTTPS). Các proxy sử dụng HTTP 1.1 để kết nối với máy chủ từ xa. Như một phương pháp an toàn chống lại yêu cầu ngẫu nhiên vòng lặp trong một ứng dụng, dịch vụ URL Fetch không thể lấy URL mà ánh xạ để xử lý yêu cầu tìm nạp. ứng dụng có thể thực hiện các kết nối với các URL khác của riêng nó, vì vậy yêu cầu các vòng lặp có thể vẫn còn, nhưng hạn chế này mang một kiểm tra sự minh mẫn đơn giản. Các yêu cầu đi có thể chứa các thông số URL, một nội dung yêu cầu, và các tiêu đề HTTP. Một vài tiêu đề không thể được sửa đổi vì lý do an ninh, trong đó chủ yếu có nghĩa là một ứng dụng không thể đưa ra một yêu cầu sai, chẳng hạn như một yêu cầu mà header Content-Length không phản ánh chính xác chiều dài nội dung thực tế của nội dung yêu cầu. Trong những trường hợp này, các dịch vụ sử dụng các giá trị chính xác, hoặc không bao gồm tiêu đề. Cả hai môi trường thời gian chạy Python và Java bao gồm giao diện đến các URL Fetch dịch vụ cạnh tranh với các thường trình thư viện chuẩn. Môi trường Python thay thế các mô-đun urllib và urllib2 với tương đương workalike sử dụng dịch vụ URL Fetch thay vì làm cho các kết nối trực tiếp. Tương tự như vậy, môi trường Java thay thế java.net.URLConnection với một thực hiện dựa trên dịch vụ. Các giao diện chuẩn cho phép mã nguồn và các thành phần bên thứ ba hiện chức năng trong một ứng dụng App Engine và vẫn đáp ứng yêu cầu mở rộng quy mô App Engine. Các giao diện này (và các đối tác của chúng ở mức độ thấp) thực hiện đồng bộ tìm nạp URL, nơi mà các ứng dụng sẽ đợi cho các máy chủ từ xa để đáp ứng trước khi ontinuing. Các dịch vụ URL Fetch cũng hỗ trợ một giao diện đồng bộ cho phép các ứng dụng để kích hoạt lấy yêu cầu, làm các công việc khác, sau đó lấy kết quả khi các yêu cầu đã sẵn sàng. Điều này có thể làm giảm đáng kể lượng thời gian đồng hồ chi tiêu trong một yêu cầu mà dựa trên một máy chủ từ xa, và cho phép ứng dụng thực hiện nhiều URL Fetch kết nối cùng lúc. Trong chương này, chúng tôi sẽ giới thiệu các giao diện tiêu chuẩn và cấp thấp cho việc
lấy URL đồng bộ Python và Java, cũng như giao diện đồng bộ cho các ứng dụng Python.
Tìm nạp URL trong Java (Fetching URLs in Java) Thời gian chạy Java bao gồm một thực hiện tuỳ chỉnh của lớp URLConnection trong gói java.net JRE tiêu chuẩn mà các lời gọi dịch vụ URL Fetch thay vì làm một kết nối socket trực tiếp. Như với các giao diện tiêu chuẩn khác, bạn có thể sử dụng giao diện này và yên tâm rằng bạn có thể chuyển ứng dụng của bạn vào nền tảng khác một cách dễ dàng. Ví dụ 10-3 cho thấy một ví dụ đơn giản của việc sử dụng một phương thức tiện lợi ở lớp URL, mà lần lượt sử dụng các lớp URLConnection, để lấy nội dung của một trang web. Phương thức openStream () của đối tượng URL trả về một dòng đầu vào của byte. Như đã trình bày, bạn có thể sử dụng một InputStreamReader (từ java.io) để xử lý các dòng byte như là một dòng kí tự. Các lớp BufferedReader làm cho nó dễ dàng để đọc dòng văn bản từ InputStreamReader. import java.net.URL; import java.net.MalformedURLException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.BufferedReader; // ... try { URL url = new URL(" />InputStream inStream = url.openStream(); InputStreamReader inStreamReader = new InputStreamReader(inStream); BufferedReader reader = new BufferedReader(inStreamReader); // ... read characters or lines with reader ... reader.close(); } catch (MalformedURLException e) { // ... } catch (IOException e) {
// ...
} Lưu ý rằng dịch vụ URL Fetch này đã đệm các phản ứng toàn bộ vào bộ nhớ của ứng dụng cùng thời điểm bộ ứng dụng bắt đầu đọc. Các ứng dụng đọc dữ liệu phản hồi từ bộ nhớ, không phải một dòng mạng từ socket hoặc dịch vụ. Bạn có thể sử dụng các tính năng khác của giao diện URLConnection, miễn là họ hoạt động trong phạm vi chức năng của dịch vụ API. Đáng chú ý, các URL Fetch dịch vụ không duy trì một kết nối liên tục với máy chủ từ xa, do đó tính năng yêu cầu một kết nối như vậy sẽ không làm việc. Theo mặc định, các dịch vụ URL Fetch đợi đến năm giây cho một phản ứng từ các máy chủ từ xa. Nếu các máy chủ không đáp ứng đúng hạn, các dịch vụ ném một IOException. Bạn có thể điều chỉnh thời lượng để chờ đợi bằng cách sử dụng phương thức setConnectTimeout () của URLConnection. (Phương thức setReadTimeout () có tác dụng tương tự; các dịch vụ sử dụng lớn hơn trong hai giá trị.) Hạn chót một là từ 1 đến 10 giây. Khi sử dụng các giao diện URLConnection, dịch vụ URL Fetch sau HTTP chuyển hướng tự động, lên đến năm lần chuyển hướng liên tiếp. API cấp thấp cho các dịch vụ URL Fetch cho phép bạn tùy chỉnh một số hành vi của dịch vụ. Ví dụ 10-4 cho thấy làm thế nào để lấy một URL với API này với các tùy chọn định. Như đã trình bày, các đối tượng FetchOptions nói với các dịch vụ không theo bất kỳ chuyển hướng, và để ném một ResponseTooLargeException nếu đáp ứng vượt quá kích thước tối đa của một megabyte thay vì cắt bỏ các dữ liệu. import java.net.URL; import java.net.MalformedURLException; import com.google.appengine.api.urlfetch.FetchOptions; import com.google.appengine.api.urlfetch.HTTPResponse; import com.google.appengine.api.urlfetch.ResponseTooLargeException; import com.google.appengine.api.urlfetch.URLFetchService; import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
// ... try {
URL url = new URL(" />FetchOptions options = FetchOptions.Builder .doNotFollowRedirects() .disallowTruncate(); HTTPRequest request = new HTTPRequest(url, HTTPMethod.GET, options); URLFetchService service = URLFetchServiceFactory.getURLFetchService(); HTTPResponse response = service.fetch(request); // ... process response.content ... } catch (ResponseTooLargeException e) { // ... } catch (MalformedURLException e) { // ... } catch (IOException e) { // ... } Các máy chủ phát triển Java mô phỏng các dịch vụ URL Fetch bằng cách làm cho các kết nối HTTP trực tiếp từ máy tính của bạn. Bảo đảm để kiểm tra rằng ứng dụng của bạn có thể truy cập vào ông URL cần thiết khi chạy trên App Engine. Chương 11: Gửi, nhận thư và thông báo tức thì (Sending and Receiving Mail and Instant Messages) ứng dụng App Engine có thể kết nối với thế giới bên ngoài trong ba cách. Phương pháp đầu tiên chúng tôi đề cập trong Chương 10: một ứng dụng có thể nhận và trả lời các yêu cầu HTTP, và một ứng dụng có thể bắt đầu yêu cầu HTTP và nhận được phản hồi với dịch vụ URL Fetch. Phương pháp thứ hai của giao tiếp kết nối mạng có sẵn cho các ứng dụng là email. Ứng dụng có thể gửi tin nhắn email bằng cách gọi các dịch vụ Mail với thông điệp dữ liệu và danh sách người nhận. Ứng dụng cũng có thể nhận tin nhắn email tại bất kỳ đâu của một
số địa chỉ theo dõi bởi App Engine.
Phương pháp thứ ba là thông báo tức thời, đặc biệt là giao thức XMPP. Ứng dụng có thể tham gia vào một hộp thoại trò chuyện với một người sử dụng của bất kỳ dịch vụ chat tương thích XMPP, bao gồm Google Talk và bất kỳ máy chủ Jabber. Ứng dụng cũng có thể sử dụng XMPP để giao tiếp với khách hàng tùy chỉnh thông qua các dịch vụ như vậy. App Engine không thực hiện như một dịch vụ XMPP của chính nó; thay vào đó, nó kết nối với cơ sở hạ tầng của Google Talk để tham gia như là một người sử dụng chat. Một ứng dụng có thể gửi email thông báo cho người sử dụng các sự kiện hệ thống hoặc các hành động của người sử dụng khác (chẳng hạn như gửi lời mời mạng xã hội), xác nhận hành động người dùng (như để xác nhận một lệnh), tiếp nối hành động của người sử dụng lâu dài (như gửi thông báo giao hàng cho một thứ tự), hoặc gửi thông báo hệ thống để quản trị viên. Các ứng dụng có thể gửi email trên danh nghĩa của chính nó, các quản trị viên của ứng dụng, hoặc người dùng hiện đã đăng nhập (trong xử lý yêu cầu đó là gửi email). Ứng dụng có thể nhận được email gửi đến các địa chỉ cụ thể, chẳng hạn như để cung cấp một giao diện email để ứng dụng, hoặc đến trung bình hoặc theo dõi các cuộc thảo luận email. Ứng dụng có thể trả lời email ngay lập tức, hoặc thiết lập công việc gây ra một trả lời được gửi sau. Các dịch vụ XMPP có giao diện hữu ích cho trò chyện, chẳng hạn như một công cụ dựa trên truy vấn trò chuyện. Với một máy khách tùy chỉnh (như một máy khách điện thoại di động, hoặc Flash trong trình duyệt) và một máy chủ XMPP hoặc chat dịch vụ (như Google Talk), các ứng dụng có thể sử dụng XMPP để đẩy cập nhật theo thời gian thực cho máy khách. App Engine không chạy máy chủ XMPP riêng của mình, vì vậy khách hàng sẽ kết nối với các dịch vụ chat (sử dụng một tài khoản trên dịch vụ đó) để giao tiếp với các ứng dụng. Gửi tin nhắn email và trò chuyện cũng tương tự như khi bắt đầu yêu cầu HTTP: ứng dụng gọi một dịch vụ sử dụng một API, và các dịch vụ hỗ trợ làm các kết nối từ xa và quản lý các giao thức thích hợp. Không giống như các dịch vụ URL Fetch, Mail và các dịch vụ
XMPP không trả lại một phản hồi tức thì.
Nhận tin nhắn email và trò chuyện cũng tương tự như nhận được yêu cầu HTTP. Trong thực tế, họ sử dụng các cơ chế tương tự: xử lý yêu cầu. Khi một dịch vụ nhận được một tin nhắn email hoặc XMPP dành cho ứng dụng của bạn, các dịch vụ sẽ gửi một yêu cầu HTTP để ứng dụng sử dụng một URL được chỉ định với các thông báo trong payload HTTP. Các ứng dụng xử lý các thông điệp đến sử dụng xử lý yêu cầu ánh xạ tới các URL cụ thể. Dịch vụ này bỏ qua các phản hồi cho yêu cầu; nếu ứng dụng cần phải trả lời cho người sử dụng, nó có thể gửi tin nhắn bằng cách sử dụng API. Hình 11-1 minh họa các luồng email gửi đến và nhắn XMPP.
Hình 11-1. Cấu trúc của thư đến và XMPP, gọi webhooks để đáp ứng với các sự kiện tin nhắn gửi đến
Mỗi ứng dụng đã thiết lập riêng của nó các địa chỉ email và XMPP, dựa trên ID ứng dụng của nó. App Engine hiện không hỗ trợ các địa chỉ email và XMPP với các lĩnh vực tùy chỉnh. Ứng dụng cũng có thể nhận tin nhắn tại địa chỉ tùy ý với một tên miền ứng dụng cụ thể. Đối với email, các ứng dụng có thể nhận tin nhắn tại địa chỉ của các hình thức:
Đối với XMPP chat, ứng dụng này có thể nhận tin nhắn tại địa chỉ của những hình thức (lưu ý sự khác biệt trong tên miền):
Trong chương này, chúng tôi sẽ thảo luận về các API cho việc gửi và nhận email và tin nhắn XMPP, và các công cụ ngôn ngữ cụ thể cho việc tạo và xử lý những tin nhắn. Kích hoạt dịch vụ trong nước (Enabling Inbound Services)
Các email gửi đến và XMPP tin nhắn được tắt theo mặc định. Nếu một ứng dụng có một dịch vụ trong nước bị vô hiệu hóa, thì sau đó tin nhắn được gửi đến địa chỉ của một ứng dụng sẽ được bỏ qua, và dịch vụ này sẽ không bao giờ tìm cách để truy cập vào các ứng dụng ở URL hook web. Trong Java, bạn thêm một phần tương tự với file appengine-web.xml, bất cứ đâu bên trong phần tử gốc: <inbound-services> <service>mail</service> <service>xmpp_message</service> </inbound-services> Những ví dụ này cho phép cả hai dịch vụ. Bạn có thể bỏ qua một dịch vụ để giữ nó vô hiệu hóa trong khi cho phép khác. Một khi ứng dụng của bạn được triển khai, bạn có thể xác nhận rằng các dịch vụ được kích hoạt từ Administration Console dưới Cài đặt ứng dụng. Nếu ứng dụng của bạn
không xuất hiện để được nhận các yêu cầu HTTP cho các tin nhắn gửi đến, kiểm tra các điều khiển và cập nhật các cấu hình nếu cần thiết. Gửi thư điện tử (Sending Email Messages) Để gửi một tin nhắn email, bạn gọi các API của dịch vụ Mail. Các tin nhắn gửi đi có một địa chỉ người gửi ( "From"), một hoặc nhiều người nhận ("To", "Cc" hoặc Bcc "), một chủ đề, một nội dung thư và file đính kèm tập tin tùy chọn. Một thông điệp gửi đi có thể chỉ có các trường này, và không thể sử dụng tiêu đề email khác. Các dịch vụ thư gắn thêm header vào tin nhắn cho mục đích theo dõi, chẳng hạn như ngày và giờ của tin nhắn được gửi. Bạn có thể chỉ định một cấu trúc nhắn nhiều phần, chẳng hạn như để bao gồm cả văn bản và phiên bản HTML đơn giản của tin nhắn, và bao gồm các file đính kèm. Tổng kích thước của tin nhắn, bao gồm tất cả các tiêu đề, không thể vượt quá một megabyte. Các lời gọi đến các dịch vụ Mail là không đồng bộ. Khi ứng dụng của bạn gọi Mail dịch vụ gửi tin nhắn, tin nhắn được hàng đợi để cung cấp, và các dịch vụ gửi trả lại. Nếu
có một vấn đề cung cấp tin nhắn, chẳng hạn như máy chủ mail từ xa không thể liên lạc hoặc các máy chủ từ xa nói địa chỉ là không hợp lệ, một thông báo lỗi sẽ được gửi qua email đến địa chỉ người gửi. Các ứng dụng không được thông báo về sự thất bại của các dịch vụ trực tiếp. (Bạn có thể sử dụng một địa chỉ email gửi đến cho các ứng dụng như địa chỉ người gửi. Các ứng dụng sẽ phải phân tích các tin nhắn được gửi bởi các máy chủ từ xa cho một lỗi.). Khi ứng dụng của bạn chạy trong các máy chủ phát triển, gửi một tin nhắn làm cho máy chủ in thông tin về tin nhắn tới các bản ghi, và tin nhắn không được gửi. Địa chỉ người gửi (Sender Addresses) Người gửi ( "From") giải quyết trên một thông báo email gửi đi phải là một trong những địa chỉ được phép: •
Địa chỉ tài khoản Google của một trong những người quản trị ứng dụng.
•
Địa chỉ của người sử dụng hiện đang đăng nhập vào các ứng dụng với tài khoản Google (trong xử lý yêu cầu đó là gửi tin nhắn).
•
Một địa chỉ email hợp lệ đến cho các ứng dụng.
Trả lời tin nhắn được gửi bởi các ứng dụng đi đến địa chỉ người gửi, cũng như các thông báo lỗi được gửi bởi máy chủ thư đi (chẳng hạn như "Không thể kết nối với máy chủ từ xa") hoặc máy chủ mail từ xa (chẳng hạn như "Người dùng không tìm thấy"). Bạn có thể sử dụng địa chỉ tài khoản Google của một nhà phát triển ứng dụng như địa chỉ người gửi. Để thêm tài khoản như quản trị viên ứng dụng, chuyển đến mục phát triển của
giao diện chính. Nếu bạn không muốn sử dụng các tài khoản của một nhà phát triển cụ thể như địa chỉ người gửi, bạn có thể tạo một tài khoản Google mới cho một địa chỉ mục đích chung, sau đó thêm nó như là một nhà phát triển cho các ứng dụng. Hãy chắc chắn rằng bạn bảo vệ mật khẩu tài khoản Google , vì bất cứ ai có thể đăng nhập vào tài khoản đó có thể sửa đổi ứng dụng của bạn. Bạn có thể sử dụng Gmail để theo dõi các tài khoản để trả lời, và bạn có thể thiết lập chuyển tiếp email tự động trong Gmail để chuyển tiếp trả lời cho các quản trị cụ thể hoặc một danh sách gửi thư (hoặc Google Group) tự động. Bạn có thể sử dụng địa chỉ email của người dùng như địa chỉ người gửi nếu và chỉ nếu địa chỉ là của một tài khoản Google được đăng ký, và người dùng được đăng nhập và bắt đầu yêu cầu mà xử lý là gửi email. Đó là, bạn có thể gửi email trên danh nghĩa của người sử dụng "hiện tại". Điều này rất hữu ích nếu email được kích hoạt bởi hành động của người dùng và nếu trả lời vào tin nhắn nên đi đến địa chỉ email của người dùng. Các tài khoản Google API không tiếp xúc với tên con người có thể đọc được của người sử dụng, do đó bạn sẽ không thể cung cấp, trừ khi bạn nhận được nó từ người sử dụng chính mình. Như chúng ta đã đề cập trước đó, một ứng dụng có thể nhận tin nhắn email tại địa chỉ của các hình thức hoặc , nơi app-id là ID ứng dụng của bạn và bất cứ điều gì có thể là chuỗi bất kỳ đó là hợp lệ ở phía bên trái của địa chỉ email (nó không thể chứa một biểu tượng @). Bạn có thể sử dụng một địa chỉ email đến như người gửi một tin nhắn email đã trả lời chuyển đến một trình xử lý yêu cầu. Các "bất cứ điều gì" cho phép bạn tạo các địa chỉ người gửi tùy chỉnh một cách nhanh chóng. Ví dụ, một ứng dụng hỗ trợ khách hàng có thể bắt đầu một cuộc trò chuyện email với một ID duy nhất và bao gồm các ID trong địa chỉ email (support+ID@app-
id.appspotmail.com), và lưu trả lời cho chuyện đó trong kho dữ liệu nên toàn bộ chủ đề có thể được xem bởi các nhân viên dịch vụ khách hàng. Lưu ý rằng địa chỉ người gửi cũng sẽ nhận được lỗi ("trả lại") tin nhắn. Nếu bạn sử dụng một địa chỉ thư đến như là người gửi, bạn có thể có những tin nhắn quá trình lỗi ứng dụng để loại bỏ các địa chỉ email không hợp lệ tự động. Lưu ý rằng các máy chủ email khác
nhau từ xa có thể sử dụng định dạng khác nhau để thông báo lỗi. Bất kỳ địa chỉ thư điện tử nào cũng có thể có một cái tên thân thiện với con người, chẳng hạn như "The Example Nhóm <>". Làm thế nào bạn làm điều này là cụ thể cho giao diện; chúng ta sẽ xem xét các giao diện trong một thời điểm. Bạn có thể bao gồm một địa chỉ riêng biệt "Reply-to" địa chỉ ngoài người gửi ( "Từ"). Hầu hết độc giả mail và máy chủ sẽ sử dụng địa chỉ này thay vì địa chỉ người gửi để trả lời và thông báo lỗi. "Reply-to" địa chỉ phải đáp ứng các yêu cầu tương tự như địa chỉ người gửi. Các máy chủ phát triển không kiểm tra xem địa chỉ người gửi đáp ứng các điều kiện bởi vì nó không biết các nhà phát triển của ứng dụng là ai. Hãy chắc chắn để kiểm tra các tính năng gửi email trong khi đang chạy trên App Engine. Người nhận (Recipients) Một thông báo email gửi đi có thể sử dụng bất kỳ địa chỉ cho một người nhận, và có thể có nhiều người nhận. Một người nhận có thể là một người nhận chính ("To"), một thứ hoặc "carbon-copy" người nhận (các "Cc" trường), hoặc một "mù carbon-copy" người nhận ("Bcc"). "To" và "Cc" người nhận được bao gồm trong nội dung của tin nhắn, do đó, một câu trả lời dành cho tất cả những người nhận có thể được gửi đến các địa chỉ có thể nhìn thấy. Những người nhận "Bcc" nhận được thông báo, nhưng địa chỉ của họ không nằm trong nội dung của tin nhắn, và do đó không được bao gồm trong bài trả lời. Các loại "Bcc" người nhận là đặc biệt hữu ích nếu bạn muốn có một tin nhắn duy nhất để đi tới nhiều người, nhưng bạn không muốn mọi người nhận biết những người nhận được tin nhắn. Bạn có thể sử dụng kỹ thuật này để gửi một bản tin email cho người dùng mà không lộ địa chỉ email của người dùng. Một kỹ thuật phổ biến cho các bản tin là sử dụng
địa chỉ người gửi là duy nhất "To" người nhận, và làm cho người khác một "Bcc" người nhận. Số lượng người nhận được một email đếm hướng tới một hạn mức nhận email. hạn mức này là ban đầu nhỏ để ngăn chặn các nhà quảng cáo email không mong muốn từ lạm
dụng hệ thống. Bạn có thể tăng hạn mức này bằng cách phân bổ phần ngân sách của bạn đối với người nhận email. Tập tin đính kèm (Attachments) Một ứng dụng có thể đính kèm tập tin vào một tin nhắn email. Vì lý do an ninh (chủ yếu là phải làm gì với các email máy khách không an toàn), chỉ có một số loại tập tin được phép đính kèm. Tên tập tin phải kết thúc bằng một phần mở rộng phản ánh loại của tập tin. Bảng 11-1 liệt kê các loại phép và tương ứng với các phần mở rộng tên tập tin cho phép. Bảng 11-1. Các loại tập tin cho phép các file đính kèm email, và phần mở rộng tên tập tin cần thiết của họ
Những loại này thường đủ cho nội dung email phong phú, nhưng không nhất thiết phải cho các tài liệu kinh doanh hoặc các ứng dụng máy tính để bàn. Nếu bạn muốn cung cấp các loại file khác cho người sử dụng, một lựa chọn là gửi một liên kết đến một trình xử lý
yêu cầu đó cung cấp các tập tin thông qua trình duyệt. Các liên kết có thể được cá nhân hoá tạm thời với một ID duy nhất, hoặc hạn chế sử dụng xác thực tài khoản Google. Gửi email trong Java (Sending Email in Java) Các giao diện Java với dịch vụ Mail là giao diện chuẩn JavaMail (javax.mail. *). Ngoài ra còn có một giao diện cấp thấp, mặc dù bạn có thể truy cập tất cả các tính năng của dịch vụ thông qua việc thực hiện JavaMail. (Như vậy, chúng tôi sẽ chỉ thảo luận về giao diện JavaMail ở đây.) Để sử dụng JavaMail, trước tiên bạn tạo một "session". JavaMail Các đối tượng Session thường chứa các thông tin cần thiết để kết nối với một máy chủ mail, nhưng với App Engine, không có cấu hình là cần thiết. Bạn chuẩn bị các thông điệp như một đối tượng MimeMessage, sau đó gửi nó bằng cách sử dụng send() phương thức tĩnh của lớp Transport. Các lớp Transport sử dụng phiên gần đây đã tạo nhất để gửi tin nhắn. import java.util.Properties; import javax.mail.Message;
import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import com.google.appengine.api.users.User; import com.google.appengine.api.users.UserServiceFactory; // ... User user = UserServiceFactory.getUserService().getCurrentUser(); String recipientAddress = user.getEmail(); Properties props = new Properties(); Session session = Session.getDefaultInstance(props, null); String messageBody = "Welcome to Example! Your account has been created." +
"You can edit your user profile by clicking the" + "following link:\n\n" + " + "Let us know if you have any questions.\n\n" + "The Example Team\n"; try { Message message = new MimeMessage(session); message.setFrom(new InternetAddress("", "The Example Team")); message.addRecipient(Message.RecipientType.TO, new InternetAddress(recipientAddress)); message.setSubject("Welcome to Example.com!"); message.setText(messageBody);
Transport.send(message); } catch (AddressException e) { // An email address was invalid. // ... } catch (MessagingException e) { Sending Email Messages | 261 // There was an error contacting the Mail service. // ... } Như đã trình bày ở đây, bạn gọi phương thức trên MimeMessage để thiết lập các trường và để thêm người nhận và nội dung. Thông điệp đơn giản nhất có một người gửi (setFrom ()), một trong "To" người nhận (addRecipient ()), một đối tượng (setSubject ()), và một cơ quan nhắn văn bản đơn giản (setText ()).
Phương thức setFrom () nhận một InternetAddress. Bạn có thể tạo một InternetAddress chỉ với địa chỉ email (String) hay địa chỉ và tên người đọc được làm tham số cho hàm tạo. Các địa chỉ email của người gửi phải đáp ứng các yêu cầu mô tả trước đó. Bạn có thể sử dụng bất kỳ chuỗi cho tên người hiểu. Phương thức addRecipient () có một loại người nhận và một InternetAddress. Các loại người nhận cho phép là Message.RecipientType.TO ("To", một người nhận cơ bản), Message.RecipientType.CC ("Cc" hoặc "carbon copy", một người nhận phụ) và Message.RecipientType.BCC ("Bcc "hoặc" carbon copy mù ", nơi người nhận gửi tin nhắn nhưng địa chỉ không xuất hiện trong nội dung tin nhắn). Bạn có thể gọi addRecipient () nhiều lần để thêm nhiều loại người nhận bất kỳ. Phương thức setText () thiết lập khối văn bản đơn giản cho tin nhắn. Để bao gồm một phiên bản HTML của khối thông báo cho bạn đọc mail hỗ trợ HTML, bạn tạo một đối tượng MimeMultipart, sau đó tạo ra một MimeBodyPart cho khối văn bản đơn giản và
một cho khối HTML và thêm chúng vào MimeMultipart. Sau đó bạn làm cho nội dung MimeMultipart của MimeMessage: import javax.mail.Multipart; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMultipart; // ... String textBody = "...text..."; String htmlBody = "...HTML..."; Multipart multipart = new MimeMultipart(); MimeBodyPart textPart = new MimeBodyPart(); textPart.setContent(textBody, "text/plain"); multipart.addBodyPart(htmlPart); MimeBodyPart htmlPart = new MimeBodyPart(); htmlPart.setContent(htmlBody, "text/html"); multipart.addBodyPart(htmlPart); message.setContent(multipart);
Bạn đính kèm tập tin vào tin nhắn email trong một cách tương tự: Multipart multipart = new MimeMultipart(); // ... byte[] fileData = getBrochureData(); String fileName = "brochure.pdf"; String fileType = "application/pdf"; MimeBodyPart attachmentPart = new MimeBodyPart(); attachmentPart.setContent(fileData, fileType); attachmentPart.setFileName(fileName); multipart.addBodyPart(attachmentPart); // ... message.setContent(multipart);
Hãy nhớ rằng một tập tin đính kèm phải có một trong các loại đã được phê duyệt, và phải có tên tập tin kết thúc bằng một phần mở rộng tên tập tin tương ứng, như được liệt kê trong Bảng 11-1. Bạn có thể thêm nhiều đối tượng MimeBodyPart một MimeMultipart duy nhất. Khôi đơn giản văn bản, khối HTML, và các tập tin đính kèm là mỗi một phần của một tin nhắn MIME nhiều phần. Khi sử dụng một MimeMultipart, bạn phải bao gồm một phần text/plain là khối văn bản đơn giản của tin nhắn. Các đối tượng nhiều phần dữ liệu ghi đè bất kỳ nội dung tập văn bản đơn giản trên MimeMessage với setText (). App Engine’s thực thi giao diện JavaMail có một lối tắt để gửi một tin nhắn email cho tất cả các cán bộ quản lý của ứng dụng. Để gửi tin nhắn đến tất cả các nhà quản lý, sử dụng một địa chỉ người nhận là "admins", không có biểu tượng @ hoặc tên miền. Tiếp nhận tin nhắn email (Receiving Email Messages) Với dịch vụ email kích nội địa hoạt trong cấu hình, một ứng dụng có thể nhận tin nhắn email tại bất kỳ của một số địa chỉ. Một tin nhắn qua thư đến sẽ được chuyển đến các ứng dụng trong các hình thức của một yêu cầu HTTP.