Restlet 指南
转载自:http://my.oschina.net/javagg/blog/3254
关于本指南
本指南的翻译工作经过了Restlet社区的官方授权,cleverpig作为贡献者完成了本文的翻译和整理工作。在此发布Matrix社区试读版的目的是为了让更多的技术爱好者阅读并提出翻译中的不足之处,以提高本指南的质量,以期修改后正式发布。
Servlet的限制
在2003年末,Jetty Web容器的作者、Servlet规范的贡献者:Greg Wilkins在其博客上对Servlet的问题进行了如下总计:
* 没有对协议与应用之间的关系进行清洗的划分。
* 由于在设计Servlet时存在对阻塞IO的假设,因此不能充分利用非阻塞NIO机制。
* 所有的Servlet Web容器对于某些应用来讲是过度设计的。
他提出构思新的API规范,使其能够真实地脱离协议,并定义能够暴露内容和元数据的contentlets。这些想法就是Restlet项目创建的灵感源泉。在之后的文章中,Greg Wilkins解释了为什么当前Servlet API限制非阻塞NIO API得到高效使用的详细理由:这种传统的用法针对每个HTTP请求都创建独立的线程进行处理。并提出了他对下一代Servlet技术的设想。
另一个主要问题就是Servlet API鼓励应用开发者在应用或者用户会话级别直接将session状态保存于内存中,尽管这看上去不错,但它造成了Servlet容器扩展性和高可用性的主要问题。为了克服这些问题,就必须实现复杂的负载均衡、session复制、持久化机制。这导致了可扩展性必然成为灾难。
Restlet简介
当复杂核心化模式日趋强大之时,面向对象设计范例已经不总是Web开发中的最佳选择,Java开发者需要认识到这一点,并且在开发新的Web服务端或是AJAX Web客户端时开始思考更加RESTfully的设计。Restlet这个开源项目为那些要采用REST结构体系来构建应用程序的Java开发者提供了一个具体的解决方案。它的非常简单易用的功能和RESTfully的Web框架,这使其成为了Web2.0开发中的又一利器。好吧,朋友们,下面就让我们开始Restlet探索之旅吧!
1. 注册一个Restlet实现
Restlet框架由两部分构成。第一部分是"Restlet API", 这个中立的API完美地实现了REST概念并简化了客户端和服务端应用的调用处理。在使用它之前,我们还需要一个支持此API的Restlet实现。Restlet的诸多实现可以通过开源项目或者商业产品获得。
API与实现的分离和Servlet API与web容器的分离(就像Jetty或Tomcat)、JDBC API与相应JDBC驱动的分离非常类似。目前,"Noelios Restlet Engine" (缩写为NRE)是Restle tAPI的参考实现之一。当下载Restlet发布版本时,API和NRE就绑定在一起,以备随时使用。如果你需要使用不同的实现,那么只需要添加JAR 文件到classpath,并删除com.noelios.restlet.jar这个NRE的JAR文件即可。
API实现的注册过程是完全自动的,如果你对此存在疑问,那么请参考JAR规范。当完成实现装载工作后,它将自动回调org.restlet.util.Engine.setInstance()方法,来进行自注册。
2. 接收Web页面的内容
正如我们在Restlet介绍中所提到的,Restlet框架即是一个客户端,又是一个服务端框架。例如,NRE能够简单地通过它的HTTP客户端 connector(连接器)访问远程资源。在REST中,connector是一种软件元素,它使两个component(组件)之间能够进行通讯,其典型的实现方式是通过某种网络协议完成通讯。NRE提供了多种客户端connector实现,这些实现都基于现存的开源项目。在connector一节中,列举出了所有可用的客户端、服务端connector,并解释了如何使用和配置它们。
下面,我们将获取一个现存资源的表示法(representation )并将其输出在JVM控制台:
// Outputting the content of a Web pageClient client = new Client(Protocol.HTTP);client.get("http://www.restlet.org").getEntity().write(System.out);
请注意上面的示例使用了最简单的方式:通过通用的客户端类(generic Client class)调用。更加灵活的方式是创建一个新的Request对象,然后请求客户端去处理它。下面的示例展示了如何在调用时设置首选项(例如 referrer URI)。当然,也可以是接收回应时的首选语言或者媒体类型:
// Prepare the requestRequest request = new Request(Method.GET, "http://www.restlet.org");request.setReferrerRef("http://www.mysite.org"); // Handle it using an HTTP client connectorClient client = new Client(Protocol.HTTP);Response response = client.handle(request); // Write the response entity on the consoleRepresentation output = response.getEntity();output.write(System.out);
3. 侦听浏览器
现在,我们将了解一下Restlet框架是如何侦听客户端请求并作出回应的。这里,我们选用了NRE HTTP服务端connector(例如基于Jetty的HTTP服务端connector),返回简单的字符串表达式“Hello World!”。请注意在更加实际的应用中,我们可以创建一个独立的类,此类继承自Restlet类,而不依靠这里的匿名内部类。
Restlet类与Servlet非常相似,并且在RESTful应用中处理调用时提供了有限的帮助。我们后面将看到一个提供了一些特定子类的框架,它能够更抽象、简单地进行处理。下面让我们先看一个简单的示例:
// Creating a minimal Restlet returning "Hello World"Restlet restlet = new Restlet() { @Override public void handle(Request request, Response response) { response.setEntity("Hello World!", MediaType.TEXT_PLAIN); }}; // Create the HTTP server and listen on port 8182new Server(Protocol.HTTP, 8182, restlet).start();
如果你运行并启动服务端,那么你可以打开浏览器输入http://localhost:8182。实际上,输入任何的URI都可以工作,你也可以尝试一下http://localhost:8182/test/tutorial。值得注意的是,如果你从另一台服务器上测试服务端,那么就需要将localhost替换为服务器的IP地址或者它的域名。
4. REST架构概述
让我们先从REST的视角审视一下典型的web架构。在下面的图表中,端口代表了connector,而后者负责component之间的通讯(组件在图中被表示为大盒子)。链接代表了用于实际通讯的特定协议(HTTP,SMTP等)。
请注意,同一个component能够具有任何数量的客户端/服务端connector。例如,Web服务器B就具有一个用于回应用户代理组件(User Agent component)的服务端connector,和多个发送请求到其它服务端的客户端connector。
5. Component、virtual hosts和applications
另外,为了支持前面所表述的标准REST软件架构元素,Restlet框架也提供了一套类:它们极大地简化了在单一JVM中部署多个应用的工作。其目的在于提供一种RESTful、可移植的、比现存的Servlet API更加灵活的框架。在下面的图表中,我们将看到三种Restlet,它们用于管理上述复杂情况:Components能够管理多个Virtual Hosts和Applications。Virtual Hosts支持灵活的配置,例如同一个IP地址能够分享多个域名、使用同一个域名实现跨越多个IP地址的负载均衡。最后,我们使用应用去管理一套相关的 Restlet、Resource、Representations。另外,应用确保了在不同Restlet实现、不同Virtual Hosts之上的可移植性和可配置性。这三种Restlet的协助为我们提供了众多的功能:譬如访问日志、请求自动解码、配置状态页设置等。
为了展示这些类,让我们尝试一个简单的示例。首先,我们创建一个component,然后在其上添加一个HTTP服务端connector,并侦听 8182端口。接着创建一个简单的、具有追踪功能的Restlet,将它放置到组件默认的Virtual Hosts上。这个默认的主机将捕捉那些没有路由到指定Virtual Hosts的请求(详见Component.hosts属性)。在后面的一个示例中,我们还将介绍应用类的使用方法。请注意,目前你并不能在控制台输出中看到任何的访问日志。
// Create a new Restlet component and add a HTTP server connector to itComponent component = new Component();component.getServers().add(Protocol.HTTP, 8182); // Create a new tracing RestletRestlet restlet = new Restlet() { @Override public void handle(Request request, Response response) { // Print the requested URI path String message = "Resource URI : " + request.getResourceRef() + ' ' + "Root URI : " + request.getRootRef() + ' ' + "Routed part : " + request.getResourceRef().getBaseRef() + ' ' + "Remaining part: " + request.getResourceRef().getRemainingPart(); response.setEntity(message, MediaType.TEXT_PLAIN); }}; // Then attach it to the local hostcomponent.getDefaultHost().attach("/trace", restlet); // Now, let's start the component!// Note that the HTTP server connector is also automatically started.component.start();
让我们通过在浏览器中输入http://localhost:8182/trace/abc/def?param=123来进行测试,得到测试结果如下:
Resource URI : http://localhost:8182/trace/abc/def?param=123Root URI : http://localhost:8182/traceRouted part : http://localhost:8182/traceRemaining part: /abc/def?param=123
6. 为静态文件提供服务
你遇到过提供静态页面(类似Javadocs)服务的web应用?如果正在使用,那么可以直接编写一个Directory类,而无需为它建立Apache服务。请见下面如何使用:
// Create a componentComponent component = new Component();component.getServers().add(Protocol.HTTP, 8182);component.getClients().add(Protocol.FILE); // Create an applicationApplication application = new Application(component.getContext()) { @Override public Restlet createRoot() { return new Directory(getContext(), ROOT_URI); }}; // Attach the application to the component and start itcomponent.getDefaultHost().attach("", application);component.start();
正如你所注意到的,我们通过传递应用的父组件上下文(context)的方式来实例化应用,而不是在第5章中提到的代码那样简单。而其主要原因是应用在分配客户端请求时,请求的分配工作需要客户端connector来完成,而后者被component所控制,并在所有被包含其中的应用之间贡献。
为了运行此示例,你需要为ROOT_URI提供一个有效值,该值依赖于你的Restlet安装路径。默认情况下,它被设置为"file:///D: /Restlet/www/docs/api/"。请注意,这里不需要任何附加的配置。如果你希望自定义在文件扩展名和元数据(metadata,包括媒体类型、语言、编码等)之间的映射,或是提供一个与众不同的索引名,你可以使用应用的“metadataService”属性。
7. 访问日志
有目的地记录web应用的活动是一种常见的需求。Restlet组件能够在默认的情况下生成类似Apache风格的日志、甚至自定义日志。通过使用 JDK内置的日志功能,logger能够配置为像任何标准JDK日志那样过滤信息、对它们进行重新格式化或者发送它们到指定位置。并且支持日志的循环(rotation);细节请查看java.util.logging包。
值得注意的是,你能够通过修改component的"logService"属性来为java.util.logging框架自定义logger名。如果希望完全掌控日志的配置,你需要通过设置系统属性来声明一个配置文件:
System.setProperty("java.util.logging.config.file", "/your/path/logging.config");
关于配置文件格式的细节,请查看JDK的LogManager类。
8. 显示错误页
另外一个常见的需求是:在调用处理过程中某些期望结果没有出现时,能够自定义返回的状态页面。也许它是某个资源没有找到或者一个可接受的表示是无效的。在这种情况下,或者遇到任何无法处理的异常时,Application或者Component将自动提供一个默认的状态页面。此服务与 org.restlet.util.StatusService类相关联,并可以作为被称为“statusService”的Application或者 Component的属性而被访问。
为了自定义默认的信息,你只需要简单地创建StatusService类的子类,并覆盖其getRepresentation(Status, Request, Response)方法。然后设置这个类的实例为指定的“statusService”属性即可。
9. 对敏感资源的访问保护
当你需要保护对某些Restlet的访问时,可以使用下面的方法:一种通用的方法是依靠cookie来识别客户端(或者客户端session),并根据你的应用状态检查给定的用户ID或者session ID,从而判断次访问是否被允许。Restlet通过访问Request或者Response中的Cookie和CookieSetting对象支持cookie。
另一种方法是基于标准HTTP认证机制。Neolios Restlet引擎目前允许基于简单HTTP方案的证书发送、接收和基于Amazon Web服务方案的证书发送。
当接收到调用时,开发者能够通过Request.challengeResponse.identifier/secret类中的Guard filter(保护过滤器)使用已经解析好的证书。过滤器是一种特殊的Restlet,它能够在调用相应Restlet之前进行预处理,或者在相应 Restlet调用返回后进行后期处理。如果你熟知Servlet API,这里的过滤器概念和Servlet API中的Filter接口非常接近。看一下我们如何修改从前的代码来对目录访问进行访问保护:
// Create a GuardGuard guard = new Guard(getContext(), ChallengeScheme.HTTP_BASIC, "Tutorial");guard.getSecrets().put("scott", "tiger".toCharArray()); // Create a Directory able to return a deep hierarchy of filesDirectory directory = new Directory(getContext(), ROOT_URI);guard.setNext(directory);return guard;
请注意:认证和授权的最终结果是完全可定制的,这只需要通过authenticate()和authorize()方法便可完成。任何自定义的机制都能够被用来检查给定的证书是否有效、通过认证的用户是否被授权继续访问相应Restlet。下面是我们简单地硬编码了用户、密码对。为了测试,我们使用了客户端Restlet API:
// Prepare the requestRequest request = new Request(Method.GET, "http://localhost:8182/"); // Add the client authentication to the callChallengeScheme scheme = ChallengeScheme.HTTP_BASIC;ChallengeResponse authentication = new ChallengeResponse(scheme, "scott", "tiger");request.setChallengeResponse(authentication); // Ask to the HTTP client connector to handle the callClient client = new Client(Protocol.HTTP);Response response = client.handle(request); if (response.getStatus().isSuccess()) { // Output the response entity on the JVM console response.getEntity().write(System.out);} else if (response.getStatus() .equals(Status.CLIENT_ERROR_UNAUTHORIZED)) { // Unauthorized access System.out .println("Access authorized by the server, " + "check your credentials");} else { // Unexpected status System.out.println("An unexpected status was returned: " + response.getStatus());}
你可以修改这里的user ID或者password,来检查服务端返回的response。请别忘记了在启动客户端之前,先执行Restlet服务端程序。请注意,如果你从另一台机器上测试服务端,那么在浏览器中输入URI时需要将"localhost"替换为服务器的IP地址或者域名。由于使用了默认接收任何类型URI的 VirtualHost,因此服务端无需任何修改。
10. URI重写和重定向
Restlet框架的另一个优点是对cool URI的内建支持。Jacob Nielsen在他的AlertBox中给出了对URI设计的重要性的绝佳描述。
首先介绍的工具是Redirector,它能够将cool URI重写为另一个URI,并接着进行相应的自动重定向。这里支持一些重定向类型:通过客户端/浏览器的外部重定向、类似代理行为的connector重定向。在下面的例子中,我们将基于Google为名为"mysite.org"的站点定义一个检索服务。与URI相关的"/search"就是检索服务,它通过"kwd"参数接收一些检索关键字:
// Create an applicationApplication application = new Application(component.getContext()) { @Override public Restlet createRoot() { // Create a Redirector to Google search service String target = "http://www.google.com/search?q=site:mysite.org+{keywords}"; return new Redirector(getContext(), target, Redirector.MODE_CLIENT_TEMPORARY); }}; // Attach the application to the component's default hostRoute route = component.getDefaultHost().attach("/search", application); // While routing requests to the application, extract a query parameter// For instance :// http://localhost:8182/search?kwd=myKeyword1+myKeyword2// will be routed to// http://www.google.com/search?q=site:mysite.org+myKeyword1%20myKeyword2route.extractQuery("keywords", "kwd", true);
请注意,Redirector只需要三个参数。第一个参数是父级上下文,第二个参数定义了如何基于URI模板重写URI。这里的URI模板将被Template类处理。第三个参数定义了重定向类型:出于简化的目的,我们选择了客户端重定向。
同时,当调用被传递给application时,我们使用了Route类从request中提取查询参数“kwd”。如果发现参数,参数将被复制到request的“keywords”属性中,以便Redirector在格式化目标URI时使用。
11. 路由器和分层URI
作为Redirector的补充,我们还具有另一个管理cool URI的工具:Router(路由器)。它们是一种特殊的Restlet,能够使其它Restlet(例如Finder和Filter)依附于它们,并基于URI模板进行自动委派调用(delegate call)。通常,你可以将Router设置为Application的根。
这里,我们将解释一下如何处理下面的URI模板:
1. /docs/ 用于显示静态文件
2. /users/{user} 用于显示用户帐号
3. /users/{user}/orders 用于显示特定用户的所有订单
4. /users/{user}/orders/{order} 用于显示特定的订单
实际上,这些URI包含了可变的部分(在大括号中)并且没有文件扩展名,这在传统的web容器中很难处理。而现在,你只需要做的只是使用URI模板将目标Restlet附着到Router上。在Restlet框架运行时,与request的URI最为匹配的Route将接收调用,并调用它所附着的 Restlet。同时,request的属性表也将自动更新为URI模板变量。
请看下面的具体实现代码。在真实的应用中,你可能希望创建单独的子类来代替我们这里使用的匿名类:
// Create a componentComponent component = new Component();component.getServers().add(Protocol.HTTP, 8182);component.getClients().add(Protocol.FILE); // Create an applicationApplication application = new Application(component.getContext()) { @Override public Restlet createRoot() { // Create a root router Router router = new Router(getContext()); // Attach a guard to secure access to the directory Guard guard = new Guard(getContext(), ChallengeScheme.HTTP_BASIC, "Restlet tutorial"); guard.getSecrets().put("scott", "tiger".toCharArray()); router.attach("/docs/", guard); // Create a directory able to expose a hierarchy of files Directory directory = new Directory(getContext(), ROOT_URI); guard.setNext(directory); // Create the account handler Restlet account = new Restlet() { @Override public void handle(Request request, Response response) { // Print the requested URI path String message = "Account of user \"" + request.getAttributes().get("user") + "\""; response.setEntity(message, MediaType.TEXT_PLAIN); } }; // Create the orders handler Restlet orders = new Restlet(getContext()) { @Override public void handle(Request request, Response response) { // Print the user name of the requested orders String message = "Orders of user \"" + request.getAttributes().get("user") + "\""; response.setEntity(message, MediaType.TEXT_PLAIN); } }; // Create the order handler Restlet order = new Restlet(getContext()) { @Override public void handle(Request request, Response response) { // Print the user name of the requested orders String message = "Order \"" + request.getAttributes().get("order") + "\" for user \"" + request.getAttributes().get("user") + "\""; response.setEntity(message, MediaType.TEXT_PLAIN); } }; // Attach the handlers to the root router router.attach("/users/{user}", account); router.attach("/users/{user}/orders", orders); router.attach("/users/{user}/orders/{order}", order); // Return the root router return router; }}; // Attach the application to the component and start itcomponent.getDefaultHost().attach(application);component.start();
请注意,变量的值是直接从URI中提取的,因此这是没有精确解码的。为了实现这样的工作,请查看手册中的decode(String)方法。
12. 抵达目标资源
在前面的示例中,在从目标URI中提取那些有趣部分时,我们利用了Restlet框架非常灵活的路由特性对request进行路由。但是,我们没有注意request方法和客户端对于它所期望的response的偏好。于是,我们如何才能将Restlet处理器和后台系统、域对象联系在一起呢?
到目前为止,我们已经介绍了一些在Restlet中超越传统Servlet API的特性。但我们并没有在"Restlet"这个框架名称中使用"REST"。如果你还没有做的话,我推荐你学习一些关于REST架构风格和将其应用于Web应用的最佳实践。这里提供了相关的FAQ记录,希望能给你一些启示,同时我们也运营着很有用的REST搜索引擎(基于Google)。如果你对传统MVC框架有一定了解,那么你可以阅读一下另一个FAQ记录,它提供了对MVC与Restlet关系的详细说明。
总结一下,request中含有标识目标资源的URI,而目标资源就是调用的主旨。这种资源信息被保存在Request.resourceRef属性中,并能够像我们之前所见那样服务于路由机制。因此在处理request时的首要目标就是发现目标资源。。。Resource类的实例或者其子类中的某个。为了帮助我们完成此项任务,我们可以使用专用的Finder,一个Restlet子类,它将Resource类引用作为参数并在request到来时自动实例化它。然后Finder将动态将调用分配给最新创建的实例,实际上就是根据request方法调用它的handle*()方法中的某一个(handleGet,handleDelete等)。当然,我们可以自定义这种行为,甚至使用Router的attach()方法,将URI模板和 Resource类作为其参数透明地创建Finder!现在,让我们看一下展示了示例中主框架类之间关系的全景图表:
回到代码中,我们在这里重构了Application.createRoot()方法。出于简化目的,我们没有提供具有静态文件的目录。你可以发现将Resource类直接指派给Router的方法。
// Create a routerRouter router = new Router(getContext()); // Attach the resources to the routerrouter.attach("/users/{user}", UserResource.class);router.attach("/users/{user}/orders", OrdersResource.class);router.attach("/users/{user}/orders/{order}", OrderResource.class); // Return the root routerreturn router;
我们最后将重审一下UserResource类。这个类继承自org.restlet.resource.Resource类,因此它覆盖了具有三个参数的构造方法。此方法初始化了"context"、"request"和"response"属性。接着,我们使用从"/users/{user} "URI模板中提取出的"user"属性,将它的值保存在一个方便使用的成员变量中。然后,我们便可以在整个application中查找与"user" 相关的域对象了。最终,我们声明了用于暴露给用户的表示变量(representation variants),在这个简单的例子中只是文字而已。它将用于在运行时透明地完成一些内容导航,以便为每个request选择适合的变量,所有这些工作都是透明的。
public class UserResource extends Resource { String userName; Object user; public UserResource(Context context, Request request, Response response) { super(context, request, response); this.userName = (String) request.getAttributes().get("user"); this.user = null; // Could be a lookup to a domain object. // Here we add the representation variants exposed getVariants().add(new Variant(MediaType.TEXT_PLAIN)); } @Override public Representation getRepresentation(Variant variant) { Representation result = null; if (variant.getMediaType().equals(MediaType.TEXT_PLAIN)) { result = new StringRepresentation("Account of user \"" + this.userName + "\""); } return result; }}
你可以查看本指南中提供的代码包并对应用进行测试,并能够以仅接受Get请求的方式获得在第十一章中的相同行为。如果你希望使用PUT方法,那么就需要在UserResource中创建一个"allowPut()"方法并简单地返回"true",并且添加一个"put (Representation)"方法来处理调用。关于详细内容请查阅Restlet的Javadocs。
结论
我们已经涵盖了Restlet框架的许多方面。在你打算行动之前,让我们先回顾一下展示了本指南的主要概念和它们之间关系的两个层次图表:
这里是核心表示类:
除了本指南,你最好的信息来源就是Restlet API的Javadocs、Restlet扩展和NRE。还可以阅读一下connector一节,它列举出了客户端和服务端connector,并解释了如何使用、配置它们。集成一节列出了提供可插入特性的所有可用扩展:例如与servlet容器的集成、动态表示的生成等。你还可以在我们的讨论组中提出问题并帮助别人。
Restlet 指南相关推荐
- 超详细中文预训练模型ERNIE使用指南-源码
作者 | 高开远,上海交通大学,自然语言处理研究方向 最近在工作上处理的都是中文语料,也尝试了一些最近放出来的预训练模型(ERNIE,BERT-CHINESE,WWM-BERT-CHINESE),比对 ...
- 入门指南目录页 -PaddlePaddle 飞桨 入门指南 FAQ合集-深度学习问题
入门指南目录页 -PaddlePaddle 飞桨 入门指南 FAQ合集 GT_Zhang关注 0.1012019.08.01 18:43:34字数 1,874阅读 795 Hi,欢迎各位来自Paddl ...
- ASIC设计-终极指南
ASIC设计-终极指南 ASIC Design – The Ultimate Guide ASIC设计-终极指南 ASICs代表特定于应用的集成电路,指的是针对特定应用而设计的半导体解决方案,与其他解 ...
- Python神经网络集成技术Guide指南
Python神经网络集成技术Guide指南 本指南将介绍如何加载一个神经网络集成系统并从Python运行推断. 提示 所有框架的神经网络集成系统运行时接口都是相同的,因此本指南适用于所有受支持框架(包 ...
- 深度学习框架集成平台C++ Guide指南
深度学习框架集成平台C++ Guide指南 这个指南详细地介绍了神经网络C++的API,并介绍了许多不同的方法来处理模型. 提示 所有框架运行时接口都是相同的,因此本指南适用于所有受支持框架(包括Te ...
- HTML5与CSS3权威指南之CSS3学习记录
title: HTML5与CSS3权威指南之CSS3学习记录 toc: true date: 2018-10-14 00:06:09 学习资料--<HTML5与CSS3权威指南>(第3版) ...
- Maven入门指南⑦:Maven的生命周期和插件
Maven入门指南⑦:Maven的生命周期和插件 一个完整的项目构建过程通常包括清理.编译.测试.打包.集成测试.验证.部署等步骤,Maven从中抽取了一套完善的.易扩展的生命周期.Maven的生命周 ...
- ini文件怎么使用_ftp文件下载工具,ftp文件下载工具是怎么使用的?使用指南
ftp文件下载工具是什么工具,可能有人会回答说不知道,因为一般只有从事网站管理的工作者会使用的多一点.但不是每个人生来就会的,所以刚开始肯定都会学习怎么使用.这篇文章就来教一下大家ftp文件下载工具是 ...
- 王建春计算机应用基础,计算机应用基础(本)教学指南.pdf
"计算机应用基础(本 )"教学指南 一.课程概况 课程性质.教学目标.教学内容等详见 "课程导学--学什么". 1.学习资源 现有教学资源 功能 "学 ...
- java修改 nsf中的数据_最新NSF申请指南中的一些重要改动
作者:孟津 美国NSF已更新其申请书的写作和提交指南,从2009年1月5日起实施.比较重要的变化有下列几点,涉及几个方面: 1. 博士后指导:每一个含有支持博士后资金的申请,必须在申请书规定的15页课 ...
最新文章
- SQL SERVER 2008的元数据视图
- 【Linux基础】查看硬件信息-内存和硬盘
- 五十二、Java连接Mysql数据库
- 乐山电子计算机职业学院,学校介绍
- java file rename 失败_java重命名文件造成文件不可读写
- Java 中Timer和TimerTask 定时器和定时任务使用的例子
- 前端学习(2995):vue+element今日头条管理--代码测试规范
- Wing IDE 5.0 破解之寻找注册码
- 智能电视直播软件_速度快,节目多,高清网络电视直播
- 关于html的一切(updating...)
- python常用代码总结-Python基础常见问题总结(一)
- iphone-common-codes-ccteam源代码 CCCommon.h
- php curl 返回cookie_分享新浪图床上传接口PHP源码
- deeplearning.ai——构建一个LR分类器来识别猫
- 工业路由器和家用路由器有什么区别?
- python编入小学教材_之前纳入小学教材的Python,现在真能学会了!
- 蔡学镛谈Java学习
- siteground主机黑五优惠最低2折-2.99美元每月-vps主机-WordPress主机服务器
- metasploit怎么用? 进阶(msfvenom=payloads+encoders)篇 (゚益゚メ) 渗透测试
- java编写一个可切换的界面_java web 项目实现手动中英文切换
热门文章
- linux查看文件格式
- 2018年注册测绘师考试详情解析
- acl审计软件_现在有多少种比较常用的审计软件 ?
- linux安装wine运行.exe文件
- vb mysql 实例,vb数据库(vb编辑access数据库实例)
- 医院门诊管理系统php文献,医院门诊管理系统(源码+系统)
- 电信测试网速测试在线软件,宽带测速在线测网速(中国电信宽带测速官网)
- 人工智能(一):技术发展史
- 快节奏多人在线游戏网络入门系列教程(4):爆头!滞后补偿
- 服务器操作系统修复补丁,最后的更新!微软Windows 7发布KB4534310修复补丁